mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-10 20:54:33 +00:00
Merge remote-tracking branch 'origin_SW/staging' into add_frost
This commit is contained in:
commit
751fc81c91
15 changed files with 501 additions and 370 deletions
|
@ -80,18 +80,32 @@ class JsonRPC {
|
|||
void _sendNextAvailableRequest() {
|
||||
_requestQueue.nextIncompleteReq.then((req) {
|
||||
if (req != null) {
|
||||
// \r\n required by electrumx server
|
||||
if (_socket != null) {
|
||||
if (!Prefs.instance.useTor) {
|
||||
if (_socket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC _sendNextAvailableRequest attempted with"
|
||||
" _socket=null on $host:$port",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
// \r\n required by electrumx server
|
||||
_socket!.write('${req.jsonRequest}\r\n');
|
||||
}
|
||||
if (_socksSocket != null) {
|
||||
_socksSocket!.write('${req.jsonRequest}\r\n');
|
||||
} else {
|
||||
if (_socksSocket == null) {
|
||||
Logging.instance.log(
|
||||
"JsonRPC _sendNextAvailableRequest attempted with"
|
||||
" _socksSocket=null on $host:$port",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
// \r\n required by electrumx server
|
||||
_socksSocket?.write('${req.jsonRequest}\r\n');
|
||||
}
|
||||
|
||||
// TODO different timeout length?
|
||||
req.initiateTimeout(
|
||||
onTimedOut: () {
|
||||
_requestQueue.remove(req);
|
||||
_onReqCompleted(req);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -109,7 +123,7 @@ class JsonRPC {
|
|||
"JsonRPC request: opening socket $host:$port",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await connect().timeout(requestTimeout, onTimeout: () {
|
||||
await _connect().timeout(requestTimeout, onTimeout: () {
|
||||
throw Exception("Request timeout: $jsonRpcRequest");
|
||||
});
|
||||
}
|
||||
|
@ -119,7 +133,7 @@ class JsonRPC {
|
|||
"JsonRPC request: opening SOCKS socket to $host:$port",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
await connect().timeout(requestTimeout, onTimeout: () {
|
||||
await _connect().timeout(requestTimeout, onTimeout: () {
|
||||
throw Exception("Request timeout: $jsonRpcRequest");
|
||||
});
|
||||
}
|
||||
|
@ -156,23 +170,42 @@ class JsonRPC {
|
|||
return future;
|
||||
}
|
||||
|
||||
Future<void> disconnect({required String reason}) async {
|
||||
await _requestMutex.protect(() async {
|
||||
await _subscription?.cancel();
|
||||
_subscription = null;
|
||||
_socket?.destroy();
|
||||
_socket = null;
|
||||
await _socksSocket?.close();
|
||||
_socksSocket = null;
|
||||
|
||||
// clean up remaining queue
|
||||
await _requestQueue.completeRemainingWithError(
|
||||
"JsonRPC disconnect() called with reason: \"$reason\"",
|
||||
);
|
||||
});
|
||||
/// DO NOT set [ignoreMutex] to true unless fully aware of the consequences
|
||||
Future<void> disconnect({
|
||||
required String reason,
|
||||
bool ignoreMutex = false,
|
||||
}) async {
|
||||
if (ignoreMutex) {
|
||||
await _disconnectHelper(reason: reason);
|
||||
} else {
|
||||
await _requestMutex.protect(() async {
|
||||
await _disconnectHelper(reason: reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> connect() async {
|
||||
Future<void> _disconnectHelper({required String reason}) async {
|
||||
await _subscription?.cancel();
|
||||
_subscription = null;
|
||||
_socket?.destroy();
|
||||
_socket = null;
|
||||
await _socksSocket?.close();
|
||||
_socksSocket = null;
|
||||
|
||||
// clean up remaining queue
|
||||
await _requestQueue.completeRemainingWithError(
|
||||
"JsonRPC disconnect() called with reason: \"$reason\"",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _connect() async {
|
||||
// ignore mutex is set to true here as _connect is already called within
|
||||
// the mutex.protect block. Setting to false here leads to a deadlock
|
||||
await disconnect(
|
||||
reason: "New connection requested",
|
||||
ignoreMutex: true,
|
||||
);
|
||||
|
||||
if (!Prefs.instance.useTor) {
|
||||
if (useSSL) {
|
||||
_socket = await SecureSocket.connect(
|
||||
|
@ -352,17 +385,20 @@ class _JsonRPCRequest {
|
|||
}
|
||||
|
||||
void initiateTimeout({
|
||||
VoidCallback? onTimedOut,
|
||||
required VoidCallback onTimedOut,
|
||||
}) {
|
||||
Future<void>.delayed(requestTimeout).then((_) {
|
||||
if (!isComplete) {
|
||||
try {
|
||||
throw JsonRpcException("_JsonRPCRequest timed out: $jsonRequest");
|
||||
} catch (e, s) {
|
||||
completer.completeError(e, s);
|
||||
onTimedOut?.call();
|
||||
}
|
||||
completer.complete(
|
||||
JsonRPCResponse(
|
||||
data: null,
|
||||
exception: JsonRpcException(
|
||||
"_JsonRPCRequest timed out: $jsonRequest",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
onTimedOut.call();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -375,14 +411,3 @@ class JsonRPCResponse {
|
|||
|
||||
JsonRPCResponse({this.data, this.exception});
|
||||
}
|
||||
|
||||
bool isIpAddress(String host) {
|
||||
try {
|
||||
// if the string can be parsed into an InternetAddress, it's an IP.
|
||||
InternetAddress(host);
|
||||
return true;
|
||||
} catch (e) {
|
||||
// if parsing fails, it's not an IP.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,82 +77,71 @@ class _NewWalletRecoveryPhraseWarningViewState
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: _buildAppBar(context),
|
||||
body: _buildBody(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAppBar(BuildContext context) {
|
||||
return isDesktop
|
||||
? const DesktopAppBar(
|
||||
isCompactHeight: false,
|
||||
leading: AppBarBackButton(),
|
||||
trailing: ExitToMyStackButton(),
|
||||
)
|
||||
: AppBar(
|
||||
leading: const AppBarBackButton(),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: AppBarIconButton(
|
||||
semanticsLabel:
|
||||
"Question Button. Opens A Dialog For Recovery Phrase Explanation.",
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
),
|
||||
onPressed: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
const RecoveryPhraseExplanationDialog(),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
final options = ref.read(pNewWalletOptions.state).state;
|
||||
|
||||
final seedCount = options?.mnemonicWordsCount ??
|
||||
Constants.defaultSeedPhraseLengthFor(coin: coin);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Center(
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: isDesktop
|
||||
? const DesktopAppBar(
|
||||
isCompactHeight: false,
|
||||
leading: AppBarBackButton(),
|
||||
trailing: ExitToMyStackButton(),
|
||||
)
|
||||
: AppBar(
|
||||
leading: const AppBarBackButton(),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: AppBarIconButton(
|
||||
semanticsLabel:
|
||||
"Question Button. Opens A Dialog For Recovery Phrase Explanation.",
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
),
|
||||
onPressed: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
const RecoveryPhraseExplanationDialog(),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: isDesktop ? 480 : double.infinity),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: isDesktop
|
||||
? CrossAxisAlignment.center
|
||||
: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (isDesktop)
|
||||
// TODO vertical centering/alignment.
|
||||
/*const Spacer(
|
||||
/*if (isDesktop)
|
||||
const Spacer(
|
||||
flex: 10,
|
||||
),*/
|
||||
if (!isDesktop)
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
if (!isDesktop)
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
if (!isDesktop)
|
||||
Text(
|
||||
walletName,
|
||||
|
@ -694,7 +683,7 @@ class _NewWalletRecoveryPhraseWarningViewState
|
|||
),*/
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -37,7 +37,7 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Address addressFor({required int index, int account = 0}) {
|
||||
String address = (cwWalletBase as MoneroWalletBase)
|
||||
String address = (CwBasedInterface.cwWalletBase as MoneroWalletBase)
|
||||
.getTransactionAddress(account, index);
|
||||
|
||||
final newReceivingAddress = Address(
|
||||
|
@ -55,16 +55,19 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Future<void> exitCwWallet() async {
|
||||
resetWalletOpenCompleter();
|
||||
(cwWalletBase as MoneroWalletBase?)?.onNewBlock = null;
|
||||
(cwWalletBase as MoneroWalletBase?)?.onNewTransaction = null;
|
||||
(cwWalletBase as MoneroWalletBase?)?.syncStatusChanged = null;
|
||||
await (cwWalletBase as MoneroWalletBase?)?.save(prioritySave: true);
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)?.onNewBlock = null;
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)?.onNewTransaction =
|
||||
null;
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)?.syncStatusChanged =
|
||||
null;
|
||||
await (CwBasedInterface.cwWalletBase as MoneroWalletBase?)
|
||||
?.save(prioritySave: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> open() async {
|
||||
resetWalletOpenCompleter();
|
||||
// await any previous exit
|
||||
await CwBasedInterface.exitMutex.protect(() async {});
|
||||
|
||||
String? password;
|
||||
try {
|
||||
|
@ -73,30 +76,32 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
throw Exception("Password not found $e, $s");
|
||||
}
|
||||
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = (await cwWalletService!.openWallet(walletId, password))
|
||||
as MoneroWalletBase;
|
||||
CwBasedInterface.cwWalletBase?.close();
|
||||
CwBasedInterface.cwWalletBase = (await CwBasedInterface.cwWalletService!
|
||||
.openWallet(walletId, password)) as MoneroWalletBase;
|
||||
|
||||
(cwWalletBase as MoneroWalletBase?)?.onNewBlock = onNewBlock;
|
||||
(cwWalletBase as MoneroWalletBase?)?.onNewTransaction = onNewTransaction;
|
||||
(cwWalletBase as MoneroWalletBase?)?.syncStatusChanged = syncStatusChanged;
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)?.onNewBlock =
|
||||
onNewBlock;
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)?.onNewTransaction =
|
||||
onNewTransaction;
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)?.syncStatusChanged =
|
||||
syncStatusChanged;
|
||||
|
||||
await updateNode();
|
||||
|
||||
await cwWalletBase?.startSync();
|
||||
await CwBasedInterface.cwWalletBase?.startSync();
|
||||
unawaited(refresh());
|
||||
autoSaveTimer?.cancel();
|
||||
autoSaveTimer = Timer.periodic(
|
||||
const Duration(seconds: 193),
|
||||
(_) async => await cwWalletBase?.save(),
|
||||
(_) async => await CwBasedInterface.cwWalletBase?.save(),
|
||||
);
|
||||
|
||||
walletOpenCompleter?.complete();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
||||
if (cwWalletBase == null || cwWalletBase?.syncStatus is! SyncedSyncStatus) {
|
||||
if (CwBasedInterface.cwWalletBase == null ||
|
||||
CwBasedInterface.cwWalletBase?.syncStatus is! SyncedSyncStatus) {
|
||||
return Amount.zeroWith(
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
@ -124,7 +129,7 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
int approximateFee = 0;
|
||||
await estimateFeeMutex.protect(() async {
|
||||
approximateFee = cwWalletBase!.calculateEstimatedFee(
|
||||
approximateFee = CwBasedInterface.cwWalletBase!.calculateEstimatedFee(
|
||||
priority,
|
||||
amount.raw.toInt(),
|
||||
);
|
||||
|
@ -138,7 +143,9 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Future<bool> pingCheck() async {
|
||||
return await (cwWalletBase as MoneroWalletBase?)?.isConnected() ?? false;
|
||||
return await (CwBasedInterface.cwWalletBase as MoneroWalletBase?)
|
||||
?.isConnected() ??
|
||||
false;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -146,7 +153,7 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
final node = getCurrentNode();
|
||||
|
||||
final host = Uri.parse(node.host).host;
|
||||
await cwWalletBase?.connectToNode(
|
||||
await CwBasedInterface.cwWalletBase?.connectToNode(
|
||||
node: Node(
|
||||
uri: "$host:${node.port}",
|
||||
type: WalletType.monero,
|
||||
|
@ -157,16 +164,15 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
try {
|
||||
await waitForWalletOpen().timeout(const Duration(seconds: 30));
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Failed to wait for wallet open: $e\n$s", level: LogLevel.Fatal);
|
||||
}
|
||||
final base = (CwBasedInterface.cwWalletBase as MoneroWalletBase?);
|
||||
|
||||
await (cwWalletBase as MoneroWalletBase?)?.updateTransactions();
|
||||
final transactions =
|
||||
(cwWalletBase as MoneroWalletBase?)?.transactionHistory?.transactions;
|
||||
if (base == null ||
|
||||
base.walletInfo.name != walletId ||
|
||||
CwBasedInterface.exitMutex.isLocked) {
|
||||
return;
|
||||
}
|
||||
await base.updateTransactions();
|
||||
final transactions = base.transactionHistory?.transactions;
|
||||
|
||||
// final cachedTransactions =
|
||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
|
||||
|
@ -210,7 +216,8 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
final addressInfo = tx.value.additionalInfo;
|
||||
|
||||
final addressString =
|
||||
(cwWalletBase as MoneroWalletBase?)?.getTransactionAddress(
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)
|
||||
?.getTransactionAddress(
|
||||
addressInfo!['accountIndex'] as int,
|
||||
addressInfo['addressIndex'] as int,
|
||||
);
|
||||
|
@ -256,15 +263,42 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
}
|
||||
}
|
||||
|
||||
await mainDB.addNewTransactionData(txnsData, walletId);
|
||||
await mainDB.isar.writeTxn(() async {
|
||||
await mainDB.isar.transactions
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.deleteAll();
|
||||
for (final data in txnsData) {
|
||||
final tx = data.item1;
|
||||
|
||||
// save transaction
|
||||
await mainDB.isar.transactions.put(tx);
|
||||
|
||||
if (data.item2 != null) {
|
||||
final address = await mainDB.getAddress(walletId, data.item2!.value);
|
||||
|
||||
// check if address exists in db and add if it does not
|
||||
if (address == null) {
|
||||
await mainDB.isar.addresses.put(data.item2!);
|
||||
}
|
||||
|
||||
// link and save address
|
||||
tx.address.value = address ?? data.item2!;
|
||||
await tx.address.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init({bool? isRestore}) async {
|
||||
cwWalletService = xmr_dart.monero
|
||||
await CwBasedInterface.exitMutex.protect(() async {});
|
||||
|
||||
CwBasedInterface.cwWalletService = xmr_dart.monero
|
||||
.createMoneroWalletService(DB.instance.moneroWalletInfoBox);
|
||||
|
||||
if (!(await cwWalletService!.isWalletExit(walletId)) && isRestore != true) {
|
||||
if (!(await CwBasedInterface.cwWalletService!.isWalletExit(walletId)) &&
|
||||
isRestore != true) {
|
||||
WalletInfo walletInfo;
|
||||
WalletCredentials credentials;
|
||||
try {
|
||||
|
@ -292,7 +326,7 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
final _walletCreationService = WalletCreationService(
|
||||
secureStorage: secureStorageInterface,
|
||||
walletService: cwWalletService,
|
||||
walletService: CwBasedInterface.cwWalletService,
|
||||
keyService: cwKeysStorage,
|
||||
);
|
||||
_walletCreationService.type = WalletType.monero;
|
||||
|
@ -328,7 +362,7 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
wallet.close();
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
cwWalletBase?.close();
|
||||
CwBasedInterface.cwWalletBase?.close();
|
||||
}
|
||||
await updateNode();
|
||||
}
|
||||
|
@ -338,14 +372,17 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) async {
|
||||
await CwBasedInterface.exitMutex.protect(() async {});
|
||||
|
||||
if (isRescan) {
|
||||
await refreshMutex.protect(() async {
|
||||
// clear blockchain info
|
||||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
|
||||
var restoreHeight = cwWalletBase?.walletInfo.restoreHeight;
|
||||
var restoreHeight =
|
||||
CwBasedInterface.cwWalletBase?.walletInfo.restoreHeight;
|
||||
highestPercentCached = 0;
|
||||
await cwWalletBase?.rescan(height: restoreHeight ?? 0);
|
||||
await CwBasedInterface.cwWalletBase?.rescan(height: restoreHeight ?? 0);
|
||||
});
|
||||
unawaited(refresh());
|
||||
return;
|
||||
|
@ -367,7 +404,7 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
isar: mainDB.isar,
|
||||
);
|
||||
|
||||
cwWalletService = xmr_dart.monero
|
||||
CwBasedInterface.cwWalletService = xmr_dart.monero
|
||||
.createMoneroWalletService(DB.instance.moneroWalletInfoBox);
|
||||
WalletInfo walletInfo;
|
||||
WalletCredentials credentials;
|
||||
|
@ -397,7 +434,7 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
final cwWalletCreationService = WalletCreationService(
|
||||
secureStorage: secureStorageInterface,
|
||||
walletService: cwWalletService,
|
||||
walletService: CwBasedInterface.cwWalletService,
|
||||
keyService: cwKeysStorage,
|
||||
);
|
||||
cwWalletCreationService.type = WalletType.monero;
|
||||
|
@ -425,15 +462,15 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
isar: mainDB.isar,
|
||||
);
|
||||
}
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = wallet as MoneroWalletBase;
|
||||
CwBasedInterface.cwWalletBase?.close();
|
||||
CwBasedInterface.cwWalletBase = wallet as MoneroWalletBase;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
}
|
||||
await updateNode();
|
||||
|
||||
await cwWalletBase?.rescan(height: credentials.height);
|
||||
cwWalletBase?.close();
|
||||
await CwBasedInterface.cwWalletBase?.rescan(height: credentials.height);
|
||||
CwBasedInterface.cwWalletBase?.close();
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from recoverFromMnemonic(): $e\n$s",
|
||||
|
@ -475,7 +512,7 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
List<monero_output.Output> outputs = [];
|
||||
for (final recipient in txData.recipients!) {
|
||||
final output = monero_output.Output(cwWalletBase!);
|
||||
final output = monero_output.Output(CwBasedInterface.cwWalletBase!);
|
||||
output.address = recipient.address;
|
||||
output.sendAll = isSendAll;
|
||||
String amountToSend = recipient.amount.decimal.toString();
|
||||
|
@ -490,7 +527,8 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
);
|
||||
|
||||
await prepareSendMutex.protect(() async {
|
||||
awaitPendingTransaction = cwWalletBase!.createTransaction(tmp);
|
||||
awaitPendingTransaction =
|
||||
CwBasedInterface.cwWalletBase!.createTransaction(tmp);
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s",
|
||||
|
@ -549,9 +587,13 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
@override
|
||||
Future<Amount> get availableBalance async {
|
||||
try {
|
||||
if (CwBasedInterface.exitMutex.isLocked) {
|
||||
throw Exception("Exit in progress");
|
||||
}
|
||||
int runningBalance = 0;
|
||||
for (final entry
|
||||
in (cwWalletBase as MoneroWalletBase?)!.balance!.entries) {
|
||||
for (final entry in (CwBasedInterface.cwWalletBase as MoneroWalletBase?)!
|
||||
.balance!
|
||||
.entries) {
|
||||
runningBalance += entry.value.unlockedBalance;
|
||||
}
|
||||
return Amount(
|
||||
|
@ -566,8 +608,13 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
@override
|
||||
Future<Amount> get totalBalance async {
|
||||
try {
|
||||
if (CwBasedInterface.exitMutex.isLocked) {
|
||||
throw Exception("Exit in progress");
|
||||
}
|
||||
final balanceEntries =
|
||||
(cwWalletBase as MoneroWalletBase?)?.balance?.entries;
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)
|
||||
?.balance
|
||||
?.entries;
|
||||
if (balanceEntries != null) {
|
||||
int bal = 0;
|
||||
for (var element in balanceEntries) {
|
||||
|
@ -578,9 +625,10 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
} else {
|
||||
final transactions = (cwWalletBase as MoneroWalletBase?)!
|
||||
.transactionHistory!
|
||||
.transactions;
|
||||
final transactions =
|
||||
(CwBasedInterface.cwWalletBase as MoneroWalletBase?)!
|
||||
.transactionHistory!
|
||||
.transactions;
|
||||
int transactionBalance = 0;
|
||||
for (var tx in transactions!.entries) {
|
||||
if (tx.value.direction == TransactionDirection.incoming) {
|
||||
|
|
|
@ -39,7 +39,7 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Address addressFor({required int index, int account = 0}) {
|
||||
String address = (cwWalletBase as WowneroWalletBase)
|
||||
String address = (CwBasedInterface.cwWalletBase as WowneroWalletBase)
|
||||
.getTransactionAddress(account, index);
|
||||
|
||||
final newReceivingAddress = Address(
|
||||
|
@ -57,7 +57,8 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
||||
if (cwWalletBase == null || cwWalletBase?.syncStatus is! SyncedSyncStatus) {
|
||||
if (CwBasedInterface.cwWalletBase == null ||
|
||||
CwBasedInterface.cwWalletBase?.syncStatus is! SyncedSyncStatus) {
|
||||
return Amount.zeroWith(
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
@ -112,7 +113,7 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
// unsure why this delay?
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
} catch (e) {
|
||||
approximateFee = cwWalletBase!.calculateEstimatedFee(
|
||||
approximateFee = CwBasedInterface.cwWalletBase!.calculateEstimatedFee(
|
||||
priority,
|
||||
amount.raw.toInt(),
|
||||
);
|
||||
|
@ -132,7 +133,9 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Future<bool> pingCheck() async {
|
||||
return await (cwWalletBase as WowneroWalletBase?)?.isConnected() ?? false;
|
||||
return await (CwBasedInterface.cwWalletBase as WowneroWalletBase?)
|
||||
?.isConnected() ??
|
||||
false;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -140,7 +143,7 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
final node = getCurrentNode();
|
||||
|
||||
final host = Uri.parse(node.host).host;
|
||||
await cwWalletBase?.connectToNode(
|
||||
await CwBasedInterface.cwWalletBase?.connectToNode(
|
||||
node: Node(
|
||||
uri: "$host:${node.port}",
|
||||
type: WalletType.wownero,
|
||||
|
@ -151,9 +154,15 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
await (cwWalletBase as WowneroWalletBase?)?.updateTransactions();
|
||||
final transactions =
|
||||
(cwWalletBase as WowneroWalletBase?)?.transactionHistory?.transactions;
|
||||
final base = (CwBasedInterface.cwWalletBase as WowneroWalletBase?);
|
||||
|
||||
if (base == null ||
|
||||
base.walletInfo.name != walletId ||
|
||||
CwBasedInterface.exitMutex.isLocked) {
|
||||
return;
|
||||
}
|
||||
await base.updateTransactions();
|
||||
final transactions = base.transactionHistory?.transactions;
|
||||
|
||||
// final cachedTransactions =
|
||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
|
||||
|
@ -197,7 +206,8 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
final addressInfo = tx.value.additionalInfo;
|
||||
|
||||
final addressString =
|
||||
(cwWalletBase as WowneroWalletBase?)?.getTransactionAddress(
|
||||
(CwBasedInterface.cwWalletBase as WowneroWalletBase?)
|
||||
?.getTransactionAddress(
|
||||
addressInfo!['accountIndex'] as int,
|
||||
addressInfo['addressIndex'] as int,
|
||||
);
|
||||
|
@ -243,15 +253,41 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
}
|
||||
}
|
||||
|
||||
await mainDB.addNewTransactionData(txnsData, walletId);
|
||||
await mainDB.isar.writeTxn(() async {
|
||||
await mainDB.isar.transactions
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.deleteAll();
|
||||
for (final data in txnsData) {
|
||||
final tx = data.item1;
|
||||
|
||||
// save transaction
|
||||
await mainDB.isar.transactions.put(tx);
|
||||
|
||||
if (data.item2 != null) {
|
||||
final address = await mainDB.getAddress(walletId, data.item2!.value);
|
||||
|
||||
// check if address exists in db and add if it does not
|
||||
if (address == null) {
|
||||
await mainDB.isar.addresses.put(data.item2!);
|
||||
}
|
||||
|
||||
// link and save address
|
||||
tx.address.value = address ?? data.item2!;
|
||||
await tx.address.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init({bool? isRestore}) async {
|
||||
cwWalletService = wow_dart.wownero
|
||||
await CwBasedInterface.exitMutex.protect(() async {});
|
||||
CwBasedInterface.cwWalletService = wow_dart.wownero
|
||||
.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
|
||||
|
||||
if (!(await cwWalletService!.isWalletExit(walletId)) && isRestore != true) {
|
||||
if (!(await CwBasedInterface.cwWalletService!.isWalletExit(walletId)) &&
|
||||
isRestore != true) {
|
||||
WalletInfo walletInfo;
|
||||
WalletCredentials credentials;
|
||||
try {
|
||||
|
@ -280,7 +316,7 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
final _walletCreationService = WalletCreationService(
|
||||
secureStorage: secureStorageInterface,
|
||||
walletService: cwWalletService,
|
||||
walletService: CwBasedInterface.cwWalletService,
|
||||
keyService: cwKeysStorage,
|
||||
);
|
||||
// _walletCreationService.changeWalletType();
|
||||
|
@ -321,7 +357,7 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
wallet.close();
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
cwWalletBase?.close();
|
||||
CwBasedInterface.cwWalletBase?.close();
|
||||
}
|
||||
await updateNode();
|
||||
}
|
||||
|
@ -331,6 +367,9 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
@override
|
||||
Future<void> open() async {
|
||||
// await any previous exit
|
||||
await CwBasedInterface.exitMutex.protect(() async {});
|
||||
|
||||
String? password;
|
||||
try {
|
||||
password = await cwKeysStorage.getWalletPassword(walletName: walletId);
|
||||
|
@ -338,43 +377,52 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
throw Exception("Password not found $e, $s");
|
||||
}
|
||||
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = (await cwWalletService!.openWallet(walletId, password))
|
||||
as WowneroWalletBase;
|
||||
CwBasedInterface.cwWalletBase?.close();
|
||||
CwBasedInterface.cwWalletBase = (await CwBasedInterface.cwWalletService!
|
||||
.openWallet(walletId, password)) as WowneroWalletBase;
|
||||
|
||||
(cwWalletBase as WowneroWalletBase?)?.onNewBlock = onNewBlock;
|
||||
(cwWalletBase as WowneroWalletBase?)?.onNewTransaction = onNewTransaction;
|
||||
(cwWalletBase as WowneroWalletBase?)?.syncStatusChanged = syncStatusChanged;
|
||||
(CwBasedInterface.cwWalletBase as WowneroWalletBase?)?.onNewBlock =
|
||||
onNewBlock;
|
||||
(CwBasedInterface.cwWalletBase as WowneroWalletBase?)?.onNewTransaction =
|
||||
onNewTransaction;
|
||||
(CwBasedInterface.cwWalletBase as WowneroWalletBase?)?.syncStatusChanged =
|
||||
syncStatusChanged;
|
||||
|
||||
await updateNode();
|
||||
|
||||
await (cwWalletBase as WowneroWalletBase?)?.startSync();
|
||||
await (CwBasedInterface.cwWalletBase as WowneroWalletBase?)?.startSync();
|
||||
unawaited(refresh());
|
||||
autoSaveTimer?.cancel();
|
||||
autoSaveTimer = Timer.periodic(
|
||||
const Duration(seconds: 193),
|
||||
(_) async => await cwWalletBase?.save(),
|
||||
(_) async => await CwBasedInterface.cwWalletBase?.save(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> exitCwWallet() async {
|
||||
(cwWalletBase as WowneroWalletBase?)?.onNewBlock = null;
|
||||
(cwWalletBase as WowneroWalletBase?)?.onNewTransaction = null;
|
||||
(cwWalletBase as WowneroWalletBase?)?.syncStatusChanged = null;
|
||||
await (cwWalletBase as WowneroWalletBase?)?.save(prioritySave: true);
|
||||
(CwBasedInterface.cwWalletBase as WowneroWalletBase?)?.onNewBlock = null;
|
||||
(CwBasedInterface.cwWalletBase as WowneroWalletBase?)?.onNewTransaction =
|
||||
null;
|
||||
(CwBasedInterface.cwWalletBase as WowneroWalletBase?)?.syncStatusChanged =
|
||||
null;
|
||||
await (CwBasedInterface.cwWalletBase as WowneroWalletBase?)
|
||||
?.save(prioritySave: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) async {
|
||||
await CwBasedInterface.exitMutex.protect(() async {});
|
||||
|
||||
if (isRescan) {
|
||||
await refreshMutex.protect(() async {
|
||||
// clear blockchain info
|
||||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
|
||||
var restoreHeight = cwWalletBase?.walletInfo.restoreHeight;
|
||||
var restoreHeight =
|
||||
CwBasedInterface.cwWalletBase?.walletInfo.restoreHeight;
|
||||
highestPercentCached = 0;
|
||||
await cwWalletBase?.rescan(height: restoreHeight ?? 0);
|
||||
await CwBasedInterface.cwWalletBase?.rescan(height: restoreHeight ?? 0);
|
||||
});
|
||||
unawaited(refresh());
|
||||
return;
|
||||
|
@ -402,7 +450,7 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
// await DB.instance
|
||||
// .put<dynamic>(boxName: walletId, key: "restoreHeight", value: height);
|
||||
|
||||
cwWalletService = wow_dart.wownero
|
||||
CwBasedInterface.cwWalletService = wow_dart.wownero
|
||||
.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
|
||||
WalletInfo walletInfo;
|
||||
WalletCredentials credentials;
|
||||
|
@ -432,7 +480,7 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
final cwWalletCreationService = WalletCreationService(
|
||||
secureStorage: secureStorageInterface,
|
||||
walletService: cwWalletService,
|
||||
walletService: CwBasedInterface.cwWalletService,
|
||||
keyService: cwKeysStorage,
|
||||
);
|
||||
cwWalletCreationService.type = WalletType.wownero;
|
||||
|
@ -442,8 +490,8 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
walletInfo.address = wallet.walletAddresses.address;
|
||||
await DB.instance
|
||||
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = wallet;
|
||||
CwBasedInterface.cwWalletBase?.close();
|
||||
CwBasedInterface.cwWalletBase = wallet;
|
||||
if (walletInfo.address != null) {
|
||||
final newReceivingAddress = await getCurrentReceivingAddress() ??
|
||||
Address(
|
||||
|
@ -467,8 +515,8 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
}
|
||||
await updateNode();
|
||||
|
||||
await cwWalletBase?.rescan(height: credentials.height);
|
||||
cwWalletBase?.close();
|
||||
await CwBasedInterface.cwWalletBase?.rescan(height: credentials.height);
|
||||
CwBasedInterface.cwWalletBase?.close();
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from recoverFromMnemonic(): $e\n$s",
|
||||
|
@ -510,7 +558,8 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
|
||||
List<wownero_output.Output> outputs = [];
|
||||
for (final recipient in txData.recipients!) {
|
||||
final output = wownero_output.Output(cwWalletBase!);
|
||||
final output =
|
||||
wownero_output.Output(CwBasedInterface.cwWalletBase!);
|
||||
output.address = recipient.address;
|
||||
output.sendAll = isSendAll;
|
||||
String amountToSend = recipient.amount.decimal.toString();
|
||||
|
@ -525,7 +574,8 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
);
|
||||
|
||||
await prepareSendMutex.protect(() async {
|
||||
awaitPendingTransaction = cwWalletBase!.createTransaction(tmp);
|
||||
awaitPendingTransaction =
|
||||
CwBasedInterface.cwWalletBase!.createTransaction(tmp);
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s",
|
||||
|
@ -584,9 +634,14 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
@override
|
||||
Future<Amount> get availableBalance async {
|
||||
try {
|
||||
if (CwBasedInterface.exitMutex.isLocked) {
|
||||
throw Exception("Exit in progress");
|
||||
}
|
||||
|
||||
int runningBalance = 0;
|
||||
for (final entry
|
||||
in (cwWalletBase as WowneroWalletBase?)!.balance!.entries) {
|
||||
for (final entry in (CwBasedInterface.cwWalletBase as WowneroWalletBase?)!
|
||||
.balance!
|
||||
.entries) {
|
||||
runningBalance += entry.value.unlockedBalance;
|
||||
}
|
||||
return Amount(
|
||||
|
@ -601,8 +656,13 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
@override
|
||||
Future<Amount> get totalBalance async {
|
||||
try {
|
||||
if (CwBasedInterface.exitMutex.isLocked) {
|
||||
throw Exception("Exit in progress");
|
||||
}
|
||||
final balanceEntries =
|
||||
(cwWalletBase as WowneroWalletBase?)?.balance?.entries;
|
||||
(CwBasedInterface.cwWalletBase as WowneroWalletBase?)
|
||||
?.balance
|
||||
?.entries;
|
||||
if (balanceEntries != null) {
|
||||
int bal = 0;
|
||||
for (var element in balanceEntries) {
|
||||
|
@ -613,7 +673,8 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
} else {
|
||||
final transactions = cwWalletBase!.transactionHistory!.transactions;
|
||||
final transactions =
|
||||
CwBasedInterface.cwWalletBase!.transactionHistory!.transactions;
|
||||
int transactionBalance = 0;
|
||||
for (var tx in transactions!.entries) {
|
||||
if (tx.value.direction == TransactionDirection.incoming) {
|
||||
|
|
|
@ -491,6 +491,11 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
),
|
||||
);
|
||||
|
||||
// add some small buffer before making calls.
|
||||
// this can probably be removed in the future but was added as a
|
||||
// debugging feature
|
||||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||
|
||||
// TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided.
|
||||
final Set<String> codesToCheck = {};
|
||||
if (this is PaynymInterface) {
|
||||
|
|
|
@ -35,8 +35,8 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
|
|||
KeyService get cwKeysStorage =>
|
||||
_cwKeysStorageCached ??= KeyService(secureStorageInterface);
|
||||
|
||||
WalletService? cwWalletService;
|
||||
WalletBase? cwWalletBase;
|
||||
static WalletService? cwWalletService;
|
||||
static WalletBase? cwWalletBase;
|
||||
|
||||
bool _hasCalledExit = false;
|
||||
bool _txRefreshLock = false;
|
||||
|
@ -46,9 +46,6 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
|
|||
double highestPercentCached = 0;
|
||||
|
||||
Timer? autoSaveTimer;
|
||||
|
||||
static bool walletOperationWaiting = false;
|
||||
|
||||
Future<String> pathForWalletDir({
|
||||
required String name,
|
||||
required WalletType type,
|
||||
|
@ -246,13 +243,6 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
|
|||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
try {
|
||||
await waitForWalletOpen().timeout(const Duration(seconds: 30));
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Failed to wait for wallet open: $e\n$s", level: LogLevel.Fatal);
|
||||
}
|
||||
|
||||
final total = await totalBalance;
|
||||
final available = await availableBalance;
|
||||
|
||||
|
@ -306,14 +296,19 @@ mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
|
|||
}
|
||||
}
|
||||
|
||||
static Mutex exitMutex = Mutex();
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
if (!_hasCalledExit) {
|
||||
resetWalletOpenCompleter();
|
||||
_hasCalledExit = true;
|
||||
autoSaveTimer?.cancel();
|
||||
await exitCwWallet();
|
||||
cwWalletBase?.close();
|
||||
await exitMutex.protect(() async {
|
||||
_hasCalledExit = true;
|
||||
autoSaveTimer?.cancel();
|
||||
await exitCwWallet();
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = null;
|
||||
cwWalletService = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1701,7 +1701,7 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
try {
|
||||
final features = await electrumXClient
|
||||
.getServerFeatures()
|
||||
.timeout(const Duration(seconds: 4));
|
||||
.timeout(const Duration(seconds: 5));
|
||||
|
||||
Logging.instance.log("features: $features", level: LogLevel.Info);
|
||||
|
||||
|
@ -1714,8 +1714,8 @@ mixin ElectrumXInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
|||
} catch (e, s) {
|
||||
// do nothing, still allow user into wallet
|
||||
Logging.instance.log(
|
||||
"$runtimeType init() failed: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
"$runtimeType init() did not complete: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.9.1+200
|
||||
version: 1.9.2+201
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.2 <4.0.0"
|
||||
|
|
|
@ -985,8 +985,8 @@ void main() {
|
|||
|
||||
expect(result, GetUsedSerialsSampleData.serials);
|
||||
|
||||
verify(mockPrefs.wifiOnly).called(1);
|
||||
verify(mockPrefs.useTor).called(1);
|
||||
verify(mockPrefs.wifiOnly).called(3);
|
||||
verify(mockPrefs.useTor).called(3);
|
||||
verifyNoMoreInteractions(mockPrefs);
|
||||
});
|
||||
|
||||
|
@ -1298,8 +1298,8 @@ void main() {
|
|||
|
||||
expect(result, GetUsedSerialsSampleData.serials);
|
||||
|
||||
verify(mockPrefs.wifiOnly).called(1);
|
||||
verify(mockPrefs.useTor).called(1);
|
||||
verify(mockPrefs.wifiOnly).called(3);
|
||||
verify(mockPrefs.useTor).called(3);
|
||||
verifyNoMoreInteractions(mockPrefs);
|
||||
});
|
||||
|
||||
|
|
|
@ -140,20 +140,18 @@ class MockJsonRPC extends _i1.Mock implements _i2.JsonRPC {
|
|||
)),
|
||||
) as _i5.Future<_i2.JsonRPCResponse>);
|
||||
@override
|
||||
_i5.Future<void> disconnect({required String? reason}) => (super.noSuchMethod(
|
||||
_i5.Future<void> disconnect({
|
||||
required String? reason,
|
||||
bool? ignoreMutex = false,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#disconnect,
|
||||
[],
|
||||
{#reason: reason},
|
||||
),
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i5.Future<void> connect() => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#connect,
|
||||
[],
|
||||
{
|
||||
#reason: reason,
|
||||
#ignoreMutex: ignoreMutex,
|
||||
},
|
||||
),
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
|
|
|
@ -55,11 +55,13 @@ void main() {
|
|||
const jsonRequestString =
|
||||
'{"jsonrpc": "2.0", "id": "some id","method": "server.ping","params": []}';
|
||||
|
||||
expect(
|
||||
() => jsonRPC.request(
|
||||
jsonRequestString,
|
||||
const Duration(seconds: 1),
|
||||
),
|
||||
throwsA(isA<SocketException>()));
|
||||
await expectLater(
|
||||
jsonRPC.request(
|
||||
jsonRequestString,
|
||||
const Duration(seconds: 1),
|
||||
),
|
||||
throwsA(isA<Exception>()
|
||||
.having((e) => e.toString(), 'message', contains("Request timeout"))),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockingjay/mockingjay.dart' as mockingjay;
|
||||
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -11,14 +10,13 @@ import '../../sample_data/theme_json.dart';
|
|||
|
||||
void main() {
|
||||
testWidgets("test DesktopDialog button pressed", (widgetTester) async {
|
||||
final key = UniqueKey();
|
||||
|
||||
final navigator = mockingjay.MockNavigator();
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
await widgetTester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [],
|
||||
child: MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
theme: ThemeData(
|
||||
extensions: [
|
||||
StackColors.fromStackColorTheme(
|
||||
|
@ -28,19 +26,19 @@ void main() {
|
|||
),
|
||||
],
|
||||
),
|
||||
home: mockingjay.MockNavigatorProvider(
|
||||
navigator: navigator,
|
||||
child: DesktopDialogCloseButton(
|
||||
key: key,
|
||||
onPressedOverride: null,
|
||||
)),
|
||||
home: DesktopDialogCloseButton(
|
||||
key: UniqueKey(),
|
||||
onPressedOverride: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await widgetTester.tap(find.byType(AppBarIconButton));
|
||||
final button = find.byType(AppBarIconButton);
|
||||
await widgetTester.tap(button);
|
||||
await widgetTester.pumpAndSettle();
|
||||
|
||||
mockingjay.verify(() => navigator.pop()).called(1);
|
||||
final navigatorState = navigatorKey.currentState;
|
||||
expect(navigatorState?.overlay, isNotNull);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -34,43 +34,43 @@ void main() {
|
|||
expect(find.text("Select emoji"), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("Emoji tapped test", (tester) async {
|
||||
const emojiSelectSheet = EmojiSelectSheet();
|
||||
|
||||
final navigator = mockingjay.MockNavigator();
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [],
|
||||
child: MaterialApp(
|
||||
theme: ThemeData(
|
||||
extensions: [
|
||||
StackColors.fromStackColorTheme(
|
||||
StackTheme.fromJson(
|
||||
json: lightThemeJsonMap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
home: mockingjay.MockNavigatorProvider(
|
||||
navigator: navigator,
|
||||
child: Column(
|
||||
children: const [
|
||||
Expanded(child: emojiSelectSheet),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final gestureDetector = find.byType(GestureDetector).at(5);
|
||||
expect(gestureDetector, findsOneWidget);
|
||||
|
||||
final emoji = Emoji.byChar("😅");
|
||||
|
||||
await tester.tap(gestureDetector);
|
||||
await tester.pumpAndSettle();
|
||||
mockingjay.verify(() => navigator.pop(emoji)).called(1);
|
||||
});
|
||||
// testWidgets("Emoji tapped test", (tester) async {
|
||||
// const emojiSelectSheet = EmojiSelectSheet();
|
||||
//
|
||||
// final navigator = mockingjay.MockNavigator();
|
||||
//
|
||||
// await tester.pumpWidget(
|
||||
// ProviderScope(
|
||||
// overrides: [],
|
||||
// child: MaterialApp(
|
||||
// theme: ThemeData(
|
||||
// extensions: [
|
||||
// StackColors.fromStackColorTheme(
|
||||
// StackTheme.fromJson(
|
||||
// json: lightThemeJsonMap,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// home: mockingjay.MockNavigatorProvider(
|
||||
// navigator: navigator,
|
||||
// child: Column(
|
||||
// children: const [
|
||||
// Expanded(child: emojiSelectSheet),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// final gestureDetector = find.byType(GestureDetector).at(5);
|
||||
// expect(gestureDetector, findsOneWidget);
|
||||
//
|
||||
// final emoji = Emoji.byChar("😅");
|
||||
//
|
||||
// await tester.tap(gestureDetector);
|
||||
// await tester.pumpAndSettle();
|
||||
// mockingjay.verify(() => navigator.pop(emoji)).called(1);
|
||||
// });
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockingjay/mockingjay.dart' as mockingjay;
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||
|
@ -15,7 +14,6 @@ import 'package:stackwallet/themes/stack_colors.dart';
|
|||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/widgets/node_options_sheet.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../sample_data/theme_json.dart';
|
||||
import 'node_options_sheet_test.mocks.dart';
|
||||
|
@ -89,48 +87,50 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets("Details tap", (tester) async {
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
final mockWallets = MockWallets();
|
||||
final mockPrefs = MockPrefs();
|
||||
final mockNodeService = MockNodeService();
|
||||
final navigator = mockingjay.MockNavigator();
|
||||
final mockTorService = MockTorService();
|
||||
|
||||
when(mockNodeService.getNodeById(id: "node id")).thenAnswer(
|
||||
(realInvocation) => NodeModel(
|
||||
host: "127.0.0.1",
|
||||
port: 2000,
|
||||
name: "Stack Default",
|
||||
id: "node id",
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: "Bitcoin",
|
||||
isFailover: false,
|
||||
isDown: false));
|
||||
(_) => NodeModel(
|
||||
host: "127.0.0.1",
|
||||
port: 2000,
|
||||
name: "Stack Default",
|
||||
id: "node id",
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: "Bitcoin",
|
||||
isFailover: false,
|
||||
isDown: false,
|
||||
),
|
||||
);
|
||||
|
||||
when(mockNodeService.getPrimaryNodeFor(coin: Coin.bitcoin)).thenAnswer(
|
||||
(realInvocation) => NodeModel(
|
||||
host: "127.0.0.1",
|
||||
port: 2000,
|
||||
name: "Stack Default",
|
||||
id: "node id",
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: "Bitcoin",
|
||||
isFailover: false,
|
||||
isDown: false));
|
||||
|
||||
mockingjay
|
||||
.when(() => navigator.pushNamed("/nodeDetails",
|
||||
arguments: const Tuple3(Coin.bitcoin, "node id", "coinNodes")))
|
||||
.thenAnswer((_) async => {});
|
||||
(_) => NodeModel(
|
||||
host: "127.0.0.1",
|
||||
port: 2000,
|
||||
name: "Stack Default",
|
||||
id: "some node id",
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: "Bitcoin",
|
||||
isFailover: false,
|
||||
isDown: false,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
pWallets.overrideWithValue(mockWallets),
|
||||
prefsChangeNotifierProvider.overrideWithValue(mockPrefs),
|
||||
nodeServiceChangeNotifierProvider.overrideWithValue(mockNodeService)
|
||||
nodeServiceChangeNotifierProvider.overrideWithValue(mockNodeService),
|
||||
pTorService.overrideWithValue(mockTorService),
|
||||
],
|
||||
child: MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
theme: ThemeData(
|
||||
extensions: [
|
||||
StackColors.fromStackColorTheme(
|
||||
|
@ -140,12 +140,17 @@ void main() {
|
|||
),
|
||||
],
|
||||
),
|
||||
home: mockingjay.MockNavigatorProvider(
|
||||
navigator: navigator,
|
||||
child: const NodeOptionsSheet(
|
||||
nodeId: "node id",
|
||||
coin: Coin.bitcoin,
|
||||
popBackToRoute: "coinNodes")),
|
||||
onGenerateRoute: (settings) {
|
||||
if (settings.name == '/nodeDetails') {
|
||||
return MaterialPageRoute(builder: (_) => Scaffold());
|
||||
}
|
||||
return null;
|
||||
},
|
||||
home: const NodeOptionsSheet(
|
||||
nodeId: "node id",
|
||||
coin: Coin.bitcoin,
|
||||
popBackToRoute: "coinNodes",
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -153,11 +158,8 @@ void main() {
|
|||
await tester.tap(find.text("Details"));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
mockingjay.verify(() => navigator.pop()).called(1);
|
||||
mockingjay
|
||||
.verify(() => navigator.pushNamed("/nodeDetails",
|
||||
arguments: const Tuple3(Coin.bitcoin, "node id", "coinNodes")))
|
||||
.called(1);
|
||||
var currentRoute = navigatorKey.currentState?.overlay?.context;
|
||||
expect(currentRoute, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets("Connect tap", (tester) async {
|
||||
|
@ -167,28 +169,32 @@ void main() {
|
|||
final mockTorService = MockTorService();
|
||||
|
||||
when(mockNodeService.getNodeById(id: "node id")).thenAnswer(
|
||||
(realInvocation) => NodeModel(
|
||||
host: "127.0.0.1",
|
||||
port: 2000,
|
||||
name: "Stack Default",
|
||||
id: "node id",
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: "Bitcoin",
|
||||
isFailover: false,
|
||||
isDown: false));
|
||||
(_) => NodeModel(
|
||||
host: "127.0.0.1",
|
||||
port: 2000,
|
||||
name: "Stack Default",
|
||||
id: "node id",
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: "Bitcoin",
|
||||
isFailover: false,
|
||||
isDown: false,
|
||||
),
|
||||
);
|
||||
|
||||
when(mockNodeService.getPrimaryNodeFor(coin: Coin.bitcoin)).thenAnswer(
|
||||
(realInvocation) => NodeModel(
|
||||
host: "127.0.0.1",
|
||||
port: 2000,
|
||||
name: "Some other node name",
|
||||
id: "some node id",
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: "Bitcoin",
|
||||
isFailover: false,
|
||||
isDown: false));
|
||||
(_) => NodeModel(
|
||||
host: "127.0.0.1",
|
||||
port: 2000,
|
||||
name: "Some other node name",
|
||||
id: "some node id",
|
||||
useSSL: true,
|
||||
enabled: true,
|
||||
coinName: "Bitcoin",
|
||||
isFailover: false,
|
||||
isDown: false,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
|
@ -209,7 +215,10 @@ void main() {
|
|||
],
|
||||
),
|
||||
home: const NodeOptionsSheet(
|
||||
nodeId: "node id", coin: Coin.bitcoin, popBackToRoute: ""),
|
||||
nodeId: "node id",
|
||||
coin: Coin.bitcoin,
|
||||
popBackToRoute: "",
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockingjay/mockingjay.dart' as mockingjay;
|
||||
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
@ -63,11 +62,13 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets("Test StackDialogOk", (widgetTester) async {
|
||||
final navigator = mockingjay.MockNavigator();
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
await widgetTester.pumpWidget(ProviderScope(
|
||||
await widgetTester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [],
|
||||
child: MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
theme: ThemeData(
|
||||
extensions: [
|
||||
StackColors.fromStackColorTheme(
|
||||
|
@ -77,23 +78,23 @@ void main() {
|
|||
),
|
||||
],
|
||||
),
|
||||
home: mockingjay.MockNavigatorProvider(
|
||||
navigator: navigator,
|
||||
child: const StackOkDialog(
|
||||
title: "Some random title",
|
||||
message: "Some message",
|
||||
leftButton: TextButton(onPressed: null, child: Text("I am left")),
|
||||
home: StackOkDialog(
|
||||
title: "Some random title",
|
||||
message: "Some message",
|
||||
leftButton: TextButton(
|
||||
onPressed: () {},
|
||||
child: const Text("I am left"),
|
||||
),
|
||||
),
|
||||
)));
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final button = find.text('I am left');
|
||||
await widgetTester.tap(button);
|
||||
await widgetTester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(StackOkDialog), findsOneWidget);
|
||||
expect(find.text("Some random title"), findsOneWidget);
|
||||
expect(find.text("Some message"), findsOneWidget);
|
||||
expect(find.byType(TextButton), findsNWidgets(2));
|
||||
|
||||
await widgetTester.tap(find.text("I am left"));
|
||||
await widgetTester.pumpAndSettle();
|
||||
final navigatorState = navigatorKey.currentState;
|
||||
expect(navigatorState?.overlay, isNotNull);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue