mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-659-Automated-Integrated-Tests
This commit is contained in:
commit
7a4ba7fd65
145 changed files with 1766 additions and 5290 deletions
|
@ -1,3 +1,4 @@
|
|||
Monero enhancements
|
||||
Synchronization improvements
|
||||
Monero synchronization improvements
|
||||
Enhance error handling
|
||||
UI enhancements
|
||||
Bug fixes
|
|
@ -1,5 +1,6 @@
|
|||
Monero and Ethereum enhancements
|
||||
Synchronization improvements
|
||||
Exchange flow enhancements
|
||||
Ledger improvements
|
||||
Wallets enhancements
|
||||
Monero synchronization improvements
|
||||
Improve wallet backups
|
||||
Enhance error handling
|
||||
UI enhancements
|
||||
Bug fixes
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||
|
@ -25,7 +25,8 @@ class BitcoinHardwareWalletService {
|
|||
for (final i in indexRange) {
|
||||
final derivationPath = "m/84'/0'/$i'";
|
||||
final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
|
||||
HDWallet hd = HDWallet.fromBase58(xpub).derive(0);
|
||||
Bip32Slip10Secp256k1 hd =
|
||||
Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
|
||||
|
||||
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
|
||||
|
||||
|
|
|
@ -2,19 +2,19 @@ import 'dart:convert';
|
|||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||
|
@ -50,11 +50,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: networkParam == null
|
||||
? bitcoin.bitcoin
|
||||
network: networkParam == null
|
||||
? BitcoinNetwork.mainnet
|
||||
: networkParam == BitcoinNetwork.mainnet
|
||||
? bitcoin.bitcoin
|
||||
: bitcoin.testnet,
|
||||
? BitcoinNetwork.mainnet
|
||||
: BitcoinNetwork.testnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
|
@ -75,10 +75,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
initialSilentAddresses: initialSilentAddresses,
|
||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.derive(1),
|
||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||
network: networkParam ?? network,
|
||||
masterHd:
|
||||
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
|
||||
masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
|
||||
);
|
||||
|
||||
autorun((_) {
|
||||
|
@ -143,49 +142,66 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
final network = walletInfo.network != null
|
||||
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
||||
: BitcoinNetwork.mainnet;
|
||||
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
|
||||
|
||||
walletInfo.derivationInfo ??= DerivationInfo(
|
||||
derivationType: snp.derivationType ?? DerivationType.electrum,
|
||||
derivationPath: snp.derivationPath,
|
||||
);
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
|
||||
ElectrumWalletSnapshot? snp = null;
|
||||
|
||||
try {
|
||||
snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
keysData =
|
||||
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||
}
|
||||
|
||||
walletInfo.derivationInfo ??= DerivationInfo();
|
||||
|
||||
// set the default if not present:
|
||||
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path;
|
||||
walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
|
||||
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
|
||||
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
|
||||
|
||||
Uint8List? seedBytes = null;
|
||||
final mnemonic = keysData.mnemonic;
|
||||
final passphrase = keysData.passphrase;
|
||||
|
||||
if (snp.mnemonic != null) {
|
||||
if (mnemonic != null) {
|
||||
switch (walletInfo.derivationInfo!.derivationType) {
|
||||
case DerivationType.electrum:
|
||||
seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic);
|
||||
break;
|
||||
case DerivationType.bip39:
|
||||
default:
|
||||
seedBytes = await bip39.mnemonicToSeed(
|
||||
snp.mnemonic!,
|
||||
passphrase: snp.passphrase ?? '',
|
||||
mnemonic,
|
||||
passphrase: passphrase ?? '',
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return BitcoinWallet(
|
||||
mnemonic: snp.mnemonic,
|
||||
xpub: snp.xpub,
|
||||
mnemonic: mnemonic,
|
||||
xpub: keysData.xPub,
|
||||
password: password,
|
||||
passphrase: snp.passphrase,
|
||||
passphrase: passphrase,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialSilentAddresses: snp.silentAddresses,
|
||||
initialSilentAddressIndex: snp.silentAddressIndex,
|
||||
initialBalance: snp.balance,
|
||||
initialAddresses: snp?.addresses,
|
||||
initialSilentAddresses: snp?.silentAddresses,
|
||||
initialSilentAddressIndex: snp?.silentAddressIndex ?? 0,
|
||||
initialBalance: snp?.balance,
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||
addressPageType: snp?.addressPageType,
|
||||
networkParam: network,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
|
@ -235,7 +251,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
|
||||
|
||||
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
|
||||
return BtcTransaction.fromRaw(hex.encode(rawHex));
|
||||
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -249,8 +265,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
||||
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
|
||||
|
||||
final signature = await _bitcoinLedgerApp!
|
||||
.signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath);
|
||||
final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!,
|
||||
message: ascii.encode(message), signDerivationPath: derivationPath);
|
||||
return base64Encode(signature);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||
import 'package:blockchain_utils/bip/bip/bip32/bip32.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -24,7 +24,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
|||
}) : super(walletInfo);
|
||||
|
||||
@override
|
||||
String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
|
||||
String getAddress(
|
||||
{required int index, required Bip32Slip10Secp256k1 hd, BitcoinAddressType? addressType}) {
|
||||
if (addressType == P2pkhAddressType.p2pkh)
|
||||
return generateP2PKHAddress(hd: hd, index: index, network: network);
|
||||
|
||||
|
|
|
@ -41,8 +41,10 @@ class BitcoinWalletService extends WalletService<
|
|||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
network: network,
|
||||
);
|
||||
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import 'package:cw_bitcoin/script_hash.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
enum ConnectionStatus { connected, disconnected, connecting, failed }
|
||||
|
||||
String jsonrpcparams(List<Object> params) {
|
||||
final _params = params.map((val) => '"${val.toString()}"').join(',');
|
||||
return '[$_params]';
|
||||
|
@ -41,7 +43,7 @@ class ElectrumClient {
|
|||
|
||||
bool get isConnected => _isConnected;
|
||||
Socket? socket;
|
||||
void Function(bool?)? onConnectionStatusChange;
|
||||
void Function(ConnectionStatus)? onConnectionStatusChange;
|
||||
int _id;
|
||||
final Map<String, SocketTask> _tasks;
|
||||
Map<String, SocketTask> get tasks => _tasks;
|
||||
|
@ -60,17 +62,33 @@ class ElectrumClient {
|
|||
}
|
||||
|
||||
Future<void> connect({required String host, required int port, bool? useSSL}) async {
|
||||
_setConnectionStatus(ConnectionStatus.connecting);
|
||||
|
||||
try {
|
||||
await socket?.close();
|
||||
} catch (_) {}
|
||||
|
||||
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||
} else {
|
||||
socket = await SecureSocket.connect(host, port,
|
||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
||||
try {
|
||||
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||
} else {
|
||||
socket = await SecureSocket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: connectionTimeout,
|
||||
onBadCertificate: (_) => true,
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return;
|
||||
}
|
||||
_setIsConnected(true);
|
||||
|
||||
if (socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return;
|
||||
}
|
||||
_setConnectionStatus(ConnectionStatus.connected);
|
||||
|
||||
socket!.listen((Uint8List event) {
|
||||
try {
|
||||
|
@ -86,13 +104,20 @@ class ElectrumClient {
|
|||
print(e.toString());
|
||||
}
|
||||
}, onError: (Object error) {
|
||||
print(error.toString());
|
||||
final errorMsg = error.toString();
|
||||
print(errorMsg);
|
||||
unterminatedString = '';
|
||||
_setIsConnected(false);
|
||||
|
||||
final currentHost = socket?.address.host;
|
||||
final isErrorForCurrentHost = errorMsg.contains(" ${currentHost} ");
|
||||
|
||||
if (currentHost != null && isErrorForCurrentHost)
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
}, onDone: () {
|
||||
unterminatedString = '';
|
||||
_setIsConnected(null);
|
||||
if (host == socket?.address.host) _setConnectionStatus(ConnectionStatus.disconnected);
|
||||
});
|
||||
|
||||
keepAlive();
|
||||
}
|
||||
|
||||
|
@ -144,9 +169,9 @@ class ElectrumClient {
|
|||
Future<void> ping() async {
|
||||
try {
|
||||
await callWithTimeout(method: 'server.ping');
|
||||
_setIsConnected(true);
|
||||
_setConnectionStatus(ConnectionStatus.connected);
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
_setIsConnected(null);
|
||||
_setConnectionStatus(ConnectionStatus.disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,37 +261,39 @@ class ElectrumClient {
|
|||
return [];
|
||||
});
|
||||
|
||||
Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async {
|
||||
Future<dynamic> getTransaction({required String hash, required bool verbose}) async {
|
||||
try {
|
||||
final result = await callWithTimeout(
|
||||
method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000);
|
||||
method: 'blockchain.transaction.get', params: [hash, verbose], timeout: 10000);
|
||||
if (result is Map<String, dynamic>) {
|
||||
return result;
|
||||
}
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
return <String, dynamic>{};
|
||||
} catch (e) {
|
||||
print("getTransactionRaw: ${e.toString()}");
|
||||
print("getTransaction: ${e.toString()}");
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
return <String, dynamic>{};
|
||||
}
|
||||
|
||||
Future<String> getTransactionHex({required String hash}) async {
|
||||
try {
|
||||
final result = await callWithTimeout(
|
||||
method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000);
|
||||
if (result is String) {
|
||||
return result;
|
||||
}
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
return '';
|
||||
} catch (e) {
|
||||
print("getTransactionHex: ${e.toString()}");
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
Future<Map<String, dynamic>> getTransactionVerbose({required String hash}) =>
|
||||
getTransaction(hash: hash, verbose: true).then((dynamic result) {
|
||||
if (result is Map<String, dynamic>) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return <String, dynamic>{};
|
||||
});
|
||||
|
||||
Future<String> getTransactionHex({required String hash}) =>
|
||||
getTransaction(hash: hash, verbose: false).then((dynamic result) {
|
||||
if (result is String) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
Future<String> broadcastTransaction(
|
||||
{required String transactionRaw,
|
||||
|
@ -348,7 +375,7 @@ class ElectrumClient {
|
|||
try {
|
||||
final topDoubleString = await estimatefee(p: 1);
|
||||
final middleDoubleString = await estimatefee(p: 5);
|
||||
final bottomDoubleString = await estimatefee(p: 100);
|
||||
final bottomDoubleString = await estimatefee(p: 10);
|
||||
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
|
||||
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
||||
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
||||
|
@ -398,6 +425,10 @@ class ElectrumClient {
|
|||
BehaviorSubject<T>? subscribe<T>(
|
||||
{required String id, required String method, List<Object> params = const []}) {
|
||||
try {
|
||||
if (socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return null;
|
||||
}
|
||||
final subscription = BehaviorSubject<T>();
|
||||
_regisrySubscription(id, subscription);
|
||||
socket!.write(jsonrpc(method: method, id: _id, params: params));
|
||||
|
@ -411,6 +442,10 @@ class ElectrumClient {
|
|||
|
||||
Future<dynamic> call(
|
||||
{required String method, List<Object> params = const [], Function(int)? idCallback}) async {
|
||||
if (socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return null;
|
||||
}
|
||||
final completer = Completer<dynamic>();
|
||||
_id += 1;
|
||||
final id = _id;
|
||||
|
@ -424,6 +459,10 @@ class ElectrumClient {
|
|||
Future<dynamic> callWithTimeout(
|
||||
{required String method, List<Object> params = const [], int timeout = 4000}) async {
|
||||
try {
|
||||
if (socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return null;
|
||||
}
|
||||
final completer = Completer<dynamic>();
|
||||
_id += 1;
|
||||
final id = _id;
|
||||
|
@ -445,6 +484,7 @@ class ElectrumClient {
|
|||
_aliveTimer?.cancel();
|
||||
try {
|
||||
await socket?.close();
|
||||
socket = null;
|
||||
} catch (_) {}
|
||||
onConnectionStatusChange = null;
|
||||
}
|
||||
|
@ -493,12 +533,9 @@ class ElectrumClient {
|
|||
}
|
||||
}
|
||||
|
||||
void _setIsConnected(bool? isConnected) {
|
||||
if (_isConnected != isConnected) {
|
||||
onConnectionStatusChange?.call(isConnected);
|
||||
}
|
||||
|
||||
_isConnected = isConnected ?? false;
|
||||
void _setConnectionStatus(ConnectionStatus status) {
|
||||
onConnectionStatusChange?.call(status);
|
||||
_isConnected = status == ConnectionStatus.connected;
|
||||
}
|
||||
|
||||
void _handleResponse(Map<String, dynamic> response) {
|
||||
|
|
|
@ -22,7 +22,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
|
||||
ElectrumTransactionInfo(this.type,
|
||||
{required String id,
|
||||
required int height,
|
||||
int? height,
|
||||
required int amount,
|
||||
int? fee,
|
||||
List<String>? inputAddresses,
|
||||
|
@ -99,7 +99,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
|
||||
factory ElectrumTransactionInfo.fromElectrumBundle(
|
||||
ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network,
|
||||
{required Set<String> addresses, required int height}) {
|
||||
{required Set<String> addresses, int? height}) {
|
||||
final date = bundle.time != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
|
||||
: DateTime.now();
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'dart:isolate';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_bitcoin/address_from_output.dart';
|
||||
|
@ -22,7 +21,6 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
|||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/exceptions.dart';
|
||||
import 'package:cw_bitcoin/litecoin_network.dart';
|
||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||
import 'package:cw_bitcoin/script_hash.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
|
@ -37,11 +35,11 @@ import 'package:cw_core/unspent_coins_info.dart';
|
|||
import 'package:cw_core/utils/file.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/get_height_by_date.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:rxdart/subjects.dart';
|
||||
import 'package:sp_scanner/sp_scanner.dart';
|
||||
|
@ -54,12 +52,12 @@ const int TWEAKS_COUNT = 25;
|
|||
|
||||
abstract class ElectrumWalletBase
|
||||
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
|
||||
with Store {
|
||||
with Store, WalletKeysFile {
|
||||
ElectrumWalletBase({
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required this.networkType,
|
||||
required this.network,
|
||||
String? xpub,
|
||||
String? mnemonic,
|
||||
Uint8List? seedBytes,
|
||||
|
@ -70,7 +68,7 @@ abstract class ElectrumWalletBase
|
|||
CryptoCurrency? currency,
|
||||
this.alwaysScan,
|
||||
}) : accountHD =
|
||||
getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo),
|
||||
getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
|
||||
syncStatus = NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_feeRates = <int>[],
|
||||
|
@ -89,8 +87,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
: {}),
|
||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||
this.network = _getNetwork(networkType, currency),
|
||||
this.isTestnet = networkType == bitcoin.testnet,
|
||||
this.isTestnet = network == BitcoinNetwork.testnet,
|
||||
this._mnemonic = mnemonic,
|
||||
super(walletInfo) {
|
||||
this.electrumClient = electrumClient ?? ElectrumClient();
|
||||
|
@ -100,12 +97,8 @@ abstract class ElectrumWalletBase
|
|||
reaction((_) => syncStatus, _syncStatusReaction);
|
||||
}
|
||||
|
||||
static bitcoin.HDWallet getAccountHDWallet(
|
||||
CryptoCurrency? currency,
|
||||
bitcoin.NetworkType networkType,
|
||||
Uint8List? seedBytes,
|
||||
String? xpub,
|
||||
DerivationInfo? derivationInfo) {
|
||||
static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
|
||||
Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
|
||||
if (seedBytes == null && xpub == null) {
|
||||
throw Exception(
|
||||
"To create a Wallet you need either a seed or an xpub. This should not happen");
|
||||
|
@ -114,25 +107,26 @@ abstract class ElectrumWalletBase
|
|||
if (seedBytes != null) {
|
||||
return currency == CryptoCurrency.bch
|
||||
? bitcoinCashHDWallet(seedBytes)
|
||||
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
|
||||
.derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path));
|
||||
: Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
|
||||
_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
|
||||
as Bip32Slip10Secp256k1;
|
||||
}
|
||||
|
||||
return bitcoin.HDWallet.fromBase58(xpub!);
|
||||
return Bip32Slip10Secp256k1.fromExtendedKey(xpub!);
|
||||
}
|
||||
|
||||
static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
|
||||
bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'");
|
||||
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
|
||||
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
|
||||
|
||||
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
||||
inputsCount * 68 + outputsCounts * 34 + 10;
|
||||
|
||||
bool? alwaysScan;
|
||||
|
||||
final bitcoin.HDWallet accountHD;
|
||||
final Bip32Slip10Secp256k1 accountHD;
|
||||
final String? _mnemonic;
|
||||
|
||||
bitcoin.HDWallet get hd => accountHD.derive(0);
|
||||
Bip32Slip10Secp256k1 get hd => accountHD.childKey(Bip32KeyIndex(0));
|
||||
final String? passphrase;
|
||||
|
||||
@override
|
||||
|
@ -164,12 +158,15 @@ abstract class ElectrumWalletBase
|
|||
.map((addr) => scriptHash(addr.address, network: network))
|
||||
.toList();
|
||||
|
||||
String get xpub => accountHD.base58!;
|
||||
String get xpub => accountHD.publicKey.toExtended;
|
||||
|
||||
@override
|
||||
String? get seed => _mnemonic;
|
||||
|
||||
bitcoin.NetworkType networkType;
|
||||
@override
|
||||
WalletKeysData get walletKeysData =>
|
||||
WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase);
|
||||
|
||||
BasedUtxoNetwork network;
|
||||
|
||||
@override
|
||||
|
@ -185,24 +182,21 @@ abstract class ElectrumWalletBase
|
|||
bool _isTryingToConnect = false;
|
||||
|
||||
@action
|
||||
Future<void> setSilentPaymentsScanning(bool active, bool usingElectrs) async {
|
||||
Future<void> setSilentPaymentsScanning(bool active) async {
|
||||
silentPaymentsScanningActive = active;
|
||||
|
||||
if (active) {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
syncStatus = StartingScanSyncStatus();
|
||||
|
||||
final tip = await getUpdatedChainTip();
|
||||
|
||||
if (tip == walletInfo.restoreHeight) {
|
||||
syncStatus = SyncedTipSyncStatus(tip);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tip > walletInfo.restoreHeight) {
|
||||
_setListeners(
|
||||
walletInfo.restoreHeight,
|
||||
chainTipParam: _currentChainTip,
|
||||
usingElectrs: usingElectrs,
|
||||
);
|
||||
_setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
|
||||
}
|
||||
} else {
|
||||
alwaysScan = false;
|
||||
|
@ -240,8 +234,11 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
BitcoinWalletKeys get keys =>
|
||||
BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
|
||||
BitcoinWalletKeys get keys => BitcoinWalletKeys(
|
||||
wif: WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer),
|
||||
privateKey: hd.privateKey.toHex(),
|
||||
publicKey: hd.publicKey.toHex(),
|
||||
);
|
||||
|
||||
String _password;
|
||||
List<BitcoinUnspent> unspentCoins;
|
||||
|
@ -273,7 +270,7 @@ abstract class ElectrumWalletBase
|
|||
int height, {
|
||||
int? chainTipParam,
|
||||
bool? doSingleScan,
|
||||
bool? usingElectrs,
|
||||
bool? usingSupportedNode,
|
||||
}) async {
|
||||
final chainTip = chainTipParam ?? await getUpdatedChainTip();
|
||||
|
||||
|
@ -282,7 +279,7 @@ abstract class ElectrumWalletBase
|
|||
return;
|
||||
}
|
||||
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
syncStatus = StartingScanSyncStatus();
|
||||
|
||||
if (_isolate != null) {
|
||||
final runningIsolate = await _isolate!;
|
||||
|
@ -300,7 +297,9 @@ abstract class ElectrumWalletBase
|
|||
chainTip: chainTip,
|
||||
electrumClient: ElectrumClient(),
|
||||
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
|
||||
node: usingElectrs == true ? ScanNode(node!.uri, node!.useSSL) : null,
|
||||
node: (await getNodeSupportsSilentPayments()) == true
|
||||
? ScanNode(node!.uri, node!.useSSL)
|
||||
: null,
|
||||
labels: walletAddresses.labels,
|
||||
labelIndexes: walletAddresses.silentAddresses
|
||||
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1)
|
||||
|
@ -388,7 +387,7 @@ abstract class ElectrumWalletBase
|
|||
BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)),
|
||||
)
|
||||
: silentAddress.B_spend,
|
||||
hrp: silentAddress.hrp,
|
||||
network: network,
|
||||
);
|
||||
|
||||
final addressRecord = walletAddresses.silentAddresses
|
||||
|
@ -417,8 +416,6 @@ abstract class ElectrumWalletBase
|
|||
await updateAllUnspents();
|
||||
await updateBalance();
|
||||
|
||||
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
|
||||
|
||||
if (alwaysScan == true) {
|
||||
_setListeners(walletInfo.restoreHeight);
|
||||
} else {
|
||||
|
@ -441,6 +438,58 @@ abstract class ElectrumWalletBase
|
|||
|
||||
Node? node;
|
||||
|
||||
Future<bool> getNodeIsElectrs() async {
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final version = await electrumClient.version();
|
||||
|
||||
if (version.isNotEmpty) {
|
||||
final server = version[0];
|
||||
|
||||
if (server.toLowerCase().contains('electrs')) {
|
||||
node!.isElectrs = true;
|
||||
node!.save();
|
||||
return node!.isElectrs!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
node!.isElectrs = false;
|
||||
node!.save();
|
||||
return node!.isElectrs!;
|
||||
}
|
||||
|
||||
Future<bool> getNodeSupportsSilentPayments() async {
|
||||
// As of today (august 2024), only ElectrumRS supports silent payments
|
||||
if (!(await getNodeIsElectrs())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final tweaksResponse = await electrumClient.getTweaks(height: 0);
|
||||
|
||||
if (tweaksResponse != null) {
|
||||
node!.supportsSilentPayments = true;
|
||||
node!.save();
|
||||
return node!.supportsSilentPayments!;
|
||||
}
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
node!.supportsSilentPayments = false;
|
||||
node!.save();
|
||||
return node!.supportsSilentPayments!;
|
||||
} catch (_) {}
|
||||
|
||||
node!.supportsSilentPayments = false;
|
||||
node!.save();
|
||||
return node!.supportsSilentPayments!;
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
|
@ -502,13 +551,6 @@ abstract class ElectrumWalletBase
|
|||
|
||||
final hd =
|
||||
utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
|
||||
final derivationPath =
|
||||
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
|
||||
"/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
|
||||
"/${utx.bitcoinAddressRecord.index}";
|
||||
final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!;
|
||||
|
||||
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
|
||||
|
||||
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
|
||||
|
@ -525,6 +567,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout));
|
||||
String pubKeyHex;
|
||||
|
||||
if (privkey != null) {
|
||||
inputPrivKeyInfos.add(ECPrivateInfo(
|
||||
|
@ -532,8 +575,18 @@ abstract class ElectrumWalletBase
|
|||
address.type == SegwitAddresType.p2tr,
|
||||
tweak: !isSilentPayment,
|
||||
));
|
||||
|
||||
pubKeyHex = privkey.getPublic().toHex();
|
||||
} else {
|
||||
pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex();
|
||||
}
|
||||
|
||||
final derivationPath =
|
||||
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
|
||||
"/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
|
||||
"/${utx.bitcoinAddressRecord.index}";
|
||||
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
|
||||
|
||||
utxos.add(
|
||||
UtxoWithAddress(
|
||||
utxo: BitcoinUtxo(
|
||||
|
@ -1076,6 +1129,11 @@ abstract class ElectrumWalletBase
|
|||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password);
|
||||
saveKeysFile(_password, true);
|
||||
}
|
||||
|
||||
final path = await makePath();
|
||||
await write(path: path, password: _password, data: toJSON());
|
||||
await transactionHistory.save();
|
||||
|
@ -1117,10 +1175,9 @@ abstract class ElectrumWalletBase
|
|||
int? chainTip,
|
||||
ScanData? scanData,
|
||||
bool? doSingleScan,
|
||||
bool? usingElectrs,
|
||||
}) async {
|
||||
silentPaymentsScanningActive = true;
|
||||
_setListeners(height, doSingleScan: doSingleScan, usingElectrs: usingElectrs);
|
||||
_setListeners(height, doSingleScan: doSingleScan);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1131,8 +1188,6 @@ abstract class ElectrumWalletBase
|
|||
_autoSaveTimer?.cancel();
|
||||
}
|
||||
|
||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
@action
|
||||
Future<void> updateAllUnspents() async {
|
||||
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||
|
@ -1220,7 +1275,7 @@ abstract class ElectrumWalletBase
|
|||
await Future.wait(unspents.map((unspent) async {
|
||||
try {
|
||||
final coin = BitcoinUnspent.fromJSON(address, unspent);
|
||||
final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
|
||||
final tx = await fetchTransactionInfo(hash: coin.hash);
|
||||
coin.isChange = address.isHidden;
|
||||
coin.confirmations = tx?.confirmations;
|
||||
|
||||
|
@ -1275,9 +1330,17 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
Future<bool> canReplaceByFee(String hash) async {
|
||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
||||
final confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
||||
final transactionHex = verboseTransaction['hex'] as String?;
|
||||
final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash);
|
||||
|
||||
final String? transactionHex;
|
||||
int confirmations = 0;
|
||||
|
||||
if (verboseTransaction.isEmpty) {
|
||||
transactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||
} else {
|
||||
confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
||||
transactionHex = verboseTransaction['hex'] as String?;
|
||||
}
|
||||
|
||||
if (confirmations > 0) return false;
|
||||
|
||||
|
@ -1285,10 +1348,7 @@ abstract class ElectrumWalletBase
|
|||
return false;
|
||||
}
|
||||
|
||||
final original = bitcoin.Transaction.fromHex(transactionHex);
|
||||
|
||||
return original.ins
|
||||
.any((element) => element.sequence != null && element.sequence! < 4294967293);
|
||||
return BtcTransaction.fromRaw(transactionHex).canReplaceByFee;
|
||||
}
|
||||
|
||||
Future<bool> isChangeSufficientForFee(String txId, int newFee) async {
|
||||
|
@ -1447,50 +1507,73 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
|
||||
Future<ElectrumTransactionBundle> getTransactionExpanded(
|
||||
{required String hash, int? height}) async {
|
||||
String transactionHex;
|
||||
// TODO: time is not always available, and calculating it from height is not always accurate.
|
||||
// Add settings to choose API provider and use and http server instead of electrum for this.
|
||||
int? time;
|
||||
int confirmations = 0;
|
||||
if (network == BitcoinNetwork.testnet) {
|
||||
// Testnet public electrum server does not support verbose transaction fetching
|
||||
int? confirmations;
|
||||
|
||||
final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash);
|
||||
|
||||
if (verboseTransaction.isEmpty) {
|
||||
transactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||
|
||||
final status = json.decode(
|
||||
(await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body);
|
||||
|
||||
time = status["block_time"] as int?;
|
||||
final height = status["block_height"] as int? ?? 0;
|
||||
final tip = await getUpdatedChainTip();
|
||||
if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0;
|
||||
} else {
|
||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
||||
|
||||
transactionHex = verboseTransaction['hex'] as String;
|
||||
time = verboseTransaction['time'] as int?;
|
||||
confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
||||
confirmations = verboseTransaction['confirmations'] as int?;
|
||||
}
|
||||
|
||||
if (height != null) {
|
||||
if (time == null) {
|
||||
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
|
||||
}
|
||||
|
||||
if (confirmations == null) {
|
||||
final tip = await getUpdatedChainTip();
|
||||
if (tip > 0 && height > 0) {
|
||||
// Add one because the block itself is the first confirmation
|
||||
confirmations = tip - height + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final original = BtcTransaction.fromRaw(transactionHex);
|
||||
final ins = <BtcTransaction>[];
|
||||
|
||||
for (final vin in original.inputs) {
|
||||
ins.add(BtcTransaction.fromRaw(await electrumClient.getTransactionHex(hash: vin.txId)));
|
||||
final verboseTransaction = await electrumClient.getTransactionVerbose(hash: vin.txId);
|
||||
|
||||
final String inputTransactionHex;
|
||||
|
||||
if (verboseTransaction.isEmpty) {
|
||||
inputTransactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||
} else {
|
||||
inputTransactionHex = verboseTransaction['hex'] as String;
|
||||
}
|
||||
|
||||
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
|
||||
}
|
||||
|
||||
return ElectrumTransactionBundle(
|
||||
original,
|
||||
ins: ins,
|
||||
time: time,
|
||||
confirmations: confirmations,
|
||||
confirmations: confirmations ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
||||
{required String hash, required int height, bool? retryOnFailure}) async {
|
||||
{required String hash, int? height, bool? retryOnFailure}) async {
|
||||
try {
|
||||
return ElectrumTransactionInfo.fromElectrumBundle(
|
||||
await getTransactionExpanded(hash: hash), walletInfo.type, network,
|
||||
addresses: addressesSet, height: height);
|
||||
await getTransactionExpanded(hash: hash, height: height),
|
||||
walletInfo.type,
|
||||
network,
|
||||
addresses: addressesSet,
|
||||
height: height,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e is FormatException && retryOnFailure == true) {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
@ -1641,8 +1724,8 @@ abstract class ElectrumWalletBase
|
|||
await getCurrentChainTip();
|
||||
|
||||
transactionHistory.transactions.values.forEach((tx) async {
|
||||
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) {
|
||||
tx.confirmations = await getCurrentChainTip() - tx.height + 1;
|
||||
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) {
|
||||
tx.confirmations = await getCurrentChainTip() - tx.height! + 1;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1758,8 +1841,12 @@ abstract class ElectrumWalletBase
|
|||
final index = address != null
|
||||
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
|
||||
: null;
|
||||
final HD = index == null ? hd : hd.derive(index);
|
||||
return base64Encode(HD.signMessage(message));
|
||||
final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
|
||||
final priv = ECPrivate.fromWif(
|
||||
WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
|
||||
netVersion: network.wifNetVer,
|
||||
);
|
||||
return priv.signMessage(StringUtils.encode(message));
|
||||
}
|
||||
|
||||
Future<void> _setInitialHeight() async {
|
||||
|
@ -1785,43 +1872,42 @@ abstract class ElectrumWalletBase
|
|||
});
|
||||
}
|
||||
|
||||
static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) {
|
||||
if (networkType == bitcoin.bitcoin && currency == CryptoCurrency.bch) {
|
||||
return BitcoinCashNetwork.mainnet;
|
||||
}
|
||||
|
||||
if (networkType == litecoinNetwork) {
|
||||
return LitecoinNetwork.mainnet;
|
||||
}
|
||||
|
||||
if (networkType == bitcoin.testnet) {
|
||||
return BitcoinNetwork.testnet;
|
||||
}
|
||||
|
||||
return BitcoinNetwork.mainnet;
|
||||
}
|
||||
|
||||
static String _hardenedDerivationPath(String derivationPath) =>
|
||||
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
|
||||
|
||||
@action
|
||||
void _onConnectionStatusChange(bool? isConnected) {
|
||||
if (syncStatus is SyncingSyncStatus) return;
|
||||
void _onConnectionStatusChange(ConnectionStatus status) {
|
||||
switch (status) {
|
||||
case ConnectionStatus.connected:
|
||||
if (syncStatus is NotConnectedSyncStatus ||
|
||||
syncStatus is LostConnectionSyncStatus ||
|
||||
syncStatus is ConnectingSyncStatus) {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
startSync();
|
||||
}
|
||||
|
||||
if (isConnected == true && syncStatus is! SyncedSyncStatus) {
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
} else if (isConnected == false) {
|
||||
syncStatus = LostConnectionSyncStatus();
|
||||
} else if (isConnected != true && syncStatus is! ConnectingSyncStatus) {
|
||||
syncStatus = NotConnectedSyncStatus();
|
||||
break;
|
||||
case ConnectionStatus.disconnected:
|
||||
syncStatus = NotConnectedSyncStatus();
|
||||
break;
|
||||
case ConnectionStatus.failed:
|
||||
syncStatus = LostConnectionSyncStatus();
|
||||
// wait for 5 seconds and then try to reconnect:
|
||||
Future.delayed(Duration(seconds: 5), () {
|
||||
electrumClient.connectToUri(
|
||||
node!.uri,
|
||||
useSSL: node!.useSSL ?? false,
|
||||
);
|
||||
});
|
||||
break;
|
||||
case ConnectionStatus.connecting:
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
void _syncStatusReaction(SyncStatus syncStatus) async {
|
||||
if (syncStatus is! AttemptingSyncStatus && syncStatus is! SyncedTipSyncStatus) {
|
||||
silentPaymentsScanningActive = syncStatus is SyncingSyncStatus;
|
||||
}
|
||||
|
||||
if (syncStatus is NotConnectedSyncStatus) {
|
||||
// Needs to re-subscribe to all scripthashes when reconnected
|
||||
_scripthashesUpdateSubject = {};
|
||||
|
@ -1942,8 +2028,8 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
final tweaks = t as Map<String, dynamic>;
|
||||
|
||||
if (tweaks["message"] != null) {
|
||||
// re-subscribe to continue receiving messages
|
||||
electrumClient.tweaksSubscribe(height: syncHeight, count: count);
|
||||
// re-subscribe to continue receiving messages, starting from the next unscanned height
|
||||
electrumClient.tweaksSubscribe(height: syncHeight + 1, count: count);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2172,3 +2258,4 @@ class UtxoDetails {
|
|||
required this.spendsUnconfirmedTX,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
|
@ -30,7 +29,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
Map<String, int>? initialChangeAddressIndex,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
int initialSilentAddressIndex = 0,
|
||||
bitcoin.HDWallet? masterHd,
|
||||
Bip32Slip10Secp256k1? masterHd,
|
||||
BitcoinAddressType? initialAddressPageType,
|
||||
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||
addressesByReceiveType =
|
||||
|
@ -53,9 +52,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
super(walletInfo) {
|
||||
if (masterHd != null) {
|
||||
silentAddress = SilentPaymentOwner.fromPrivateKeys(
|
||||
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!),
|
||||
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
|
||||
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
|
||||
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()),
|
||||
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()),
|
||||
network: network,
|
||||
);
|
||||
|
||||
if (silentAddresses.length == 0) {
|
||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||
|
@ -92,8 +92,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
final BasedUtxoNetwork network;
|
||||
final bitcoin.HDWallet mainHd;
|
||||
final bitcoin.HDWallet sideHd;
|
||||
final Bip32Slip10Secp256k1 mainHd;
|
||||
final Bip32Slip10Secp256k1 sideHd;
|
||||
|
||||
@observable
|
||||
SilentPaymentOwner? silentAddress;
|
||||
|
@ -318,7 +318,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
}
|
||||
|
||||
String getAddress(
|
||||
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
||||
{required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType}) =>
|
||||
'';
|
||||
|
||||
@override
|
||||
|
@ -540,11 +542,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
void _validateAddresses() {
|
||||
_addresses.forEach((element) {
|
||||
if (!element.isHidden && element.address !=
|
||||
getAddress(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
if (!element.isHidden &&
|
||||
element.address !=
|
||||
getAddress(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
element.isHidden = true;
|
||||
} else if (element.isHidden && element.address !=
|
||||
getAddress(index: element.index, hd: sideHd, addressType: element.type)) {
|
||||
} else if (element.isHidden &&
|
||||
element.address !=
|
||||
getAddress(index: element.index, hd: sideHd, addressType: element.type)) {
|
||||
element.isHidden = false;
|
||||
}
|
||||
});
|
||||
|
@ -562,7 +566,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
return _isAddressByType(addressRecord, addressPageType);
|
||||
}
|
||||
|
||||
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
||||
Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
||||
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
||||
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
|
||||
!addr.isHidden && !addr.isUsed && addr.type == type;
|
||||
|
|
|
@ -32,15 +32,21 @@ class ElectrumWalletSnapshot {
|
|||
final WalletType type;
|
||||
final String? addressPageType;
|
||||
|
||||
@deprecated
|
||||
String? mnemonic;
|
||||
|
||||
@deprecated
|
||||
String? xpub;
|
||||
|
||||
@deprecated
|
||||
String? passphrase;
|
||||
|
||||
List<BitcoinAddressRecord> addresses;
|
||||
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
ElectrumBalance balance;
|
||||
Map<String, int> regularAddressIndex;
|
||||
Map<String, int> changeAddressIndex;
|
||||
int silentAddressIndex;
|
||||
String? passphrase;
|
||||
DerivationType? derivationType;
|
||||
String? derivationPath;
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||
|
||||
final litecoinNetwork = NetworkType(
|
||||
messagePrefix: '\x19Litecoin Signed Message:\n',
|
||||
bech32: 'ltc',
|
||||
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
|
||||
pubKeyHash: 0x30,
|
||||
scriptHash: 0x32,
|
||||
wif: 0xb0);
|
|
@ -1,20 +1,21 @@
|
|||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/litecoin_network.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
part 'litecoin_wallet.g.dart';
|
||||
|
||||
|
@ -37,7 +38,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: litecoinNetwork,
|
||||
network: LitecoinNetwork.mainnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
|
@ -48,7 +49,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.derive(1),
|
||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||
network: network,
|
||||
);
|
||||
autorun((_) {
|
||||
|
@ -101,19 +102,37 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required String password,
|
||||
}) async {
|
||||
final snp =
|
||||
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
|
||||
ElectrumWalletSnapshot? snp = null;
|
||||
|
||||
try {
|
||||
snp = await ElectrumWalletSnapshot.load(
|
||||
name, walletInfo.type, password, LitecoinNetwork.mainnet);
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
keysData =
|
||||
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||
}
|
||||
|
||||
return LitecoinWallet(
|
||||
mnemonic: snp.mnemonic!,
|
||||
mnemonic: keysData.mnemonic!,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialBalance: snp.balance,
|
||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic!),
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
initialAddresses: snp?.addresses,
|
||||
initialBalance: snp?.balance,
|
||||
seedBytes: await mnemonicToSeedBytes(keysData.mnemonic!),
|
||||
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||
addressPageType: snp?.addressPageType,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -22,6 +22,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
|
||||
@override
|
||||
String getAddress(
|
||||
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
||||
{required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType}) =>
|
||||
generateP2WPKHAddress(hd: hd, index: index, network: network);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ class LitecoinWalletService extends WalletService<
|
|||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
|
|
|
@ -1,68 +1,54 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||
import 'package:hex/hex.dart';
|
||||
|
||||
bitcoin.PaymentData generatePaymentData({
|
||||
required bitcoin.HDWallet hd,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
|
||||
}
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
|
||||
ECPrivate generateECPrivate({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final wif = hd.derive(index).wif!;
|
||||
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
|
||||
}
|
||||
}) =>
|
||||
ECPrivate(hd.childKey(Bip32KeyIndex(index)).privateKey);
|
||||
|
||||
String generateP2WPKHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toP2wpkhAddress()
|
||||
.toAddress(network);
|
||||
|
||||
String generateP2SHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toP2wshInP2sh()
|
||||
.toAddress(network);
|
||||
|
||||
String generateP2WSHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toP2wshAddress()
|
||||
.toAddress(network);
|
||||
|
||||
String generateP2PKHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toP2pkhAddress()
|
||||
.toAddress(network);
|
||||
|
||||
String generateP2TRAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toTaprootAddress()
|
||||
.toAddress(network);
|
||||
|
|
|
@ -41,15 +41,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
bech32:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "cake-0.2.2"
|
||||
resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192"
|
||||
url: "https://github.com/cake-tech/bech32.git"
|
||||
source: git
|
||||
version: "0.2.2"
|
||||
bip32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -79,29 +70,20 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v3
|
||||
resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149
|
||||
ref: cake-update-v4
|
||||
resolved-ref: "574486bfcdbbaf978dcd006b46fc8716f880da29"
|
||||
url: "https://github.com/cake-tech/bitcoin_base"
|
||||
source: git
|
||||
version: "4.2.1"
|
||||
bitcoin_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v4
|
||||
resolved-ref: e19ffb7e7977278a75b27e0479b3c6f4034223b3
|
||||
url: "https://github.com/cake-tech/bitcoin_flutter.git"
|
||||
source: git
|
||||
version: "2.1.0"
|
||||
version: "4.7.0"
|
||||
blockchain_utils:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v1
|
||||
resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
|
||||
url: "https://github.com/cake-tech/blockchain_utils"
|
||||
source: git
|
||||
version: "2.1.2"
|
||||
version: "3.3.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -411,10 +393,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.2"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -499,11 +481,12 @@ packages:
|
|||
ledger_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: ledger_flutter
|
||||
sha256: f1680060ed6ff78f275837e0024ccaf667715a59ba7aa29fa7354bc7752e71c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path: "."
|
||||
ref: cake-v3
|
||||
resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4"
|
||||
url: "https://github.com/cake-tech/ledger-flutter.git"
|
||||
source: git
|
||||
version: "1.0.2"
|
||||
ledger_usb:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -596,10 +579,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.4"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -636,18 +619,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "3.1.5"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -700,10 +683,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
|
||||
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
version: "1.3.0"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -761,10 +744,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: socks5_proxy
|
||||
sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83"
|
||||
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5+dev.2"
|
||||
version: "1.0.6"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -793,9 +776,9 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "sp_v2.1.0"
|
||||
resolved-ref: b4b8fbd8d832198d3cee853feb427c4ec482dd7d
|
||||
url: "https://github.com/cake-tech/sp_scanner"
|
||||
ref: "sp_v4.0.0"
|
||||
resolved-ref: "3b8ae38592c0584f53560071dc18bc570758fe13"
|
||||
url: "https://github.com/rafael-xmr/sp_scanner"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
stack_trace:
|
||||
|
@ -910,14 +893,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.5"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -19,10 +19,6 @@ dependencies:
|
|||
intl: ^0.18.0
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
bitcoin_flutter:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
||||
ref: cake-update-v4
|
||||
bitbox:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||
|
@ -32,19 +28,19 @@ dependencies:
|
|||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-update-v3
|
||||
ref: cake-update-v4
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v1
|
||||
ref: cake-update-v2
|
||||
ledger_flutter: ^1.0.1
|
||||
ledger_bitcoin:
|
||||
git:
|
||||
url: https://github.com/cake-tech/ledger-bitcoin
|
||||
sp_scanner:
|
||||
git:
|
||||
url: https://github.com/cake-tech/sp_scanner
|
||||
ref: sp_v2.1.0
|
||||
git:
|
||||
url: https://github.com/rafael-xmr/sp_scanner
|
||||
ref: sp_v4.0.0
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
|
@ -12,6 +10,7 @@ import 'package:cw_core/crypto_currency.dart';
|
|||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
@ -39,7 +38,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: bitcoin.bitcoin,
|
||||
network: BitcoinCashNetwork.mainnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
|
@ -50,7 +49,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.derive(1),
|
||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||
network: network,
|
||||
initialAddressPageType: addressPageType,
|
||||
);
|
||||
|
@ -76,7 +75,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: await Mnemonic.toSeed(mnemonic),
|
||||
seedBytes: await MnemonicBip39.toSeed(mnemonic),
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
addressPageType: P2pkhAddressType.p2pkh,
|
||||
|
@ -89,14 +88,32 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required String password,
|
||||
}) async {
|
||||
final snp = await ElectrumWalletSnapshot.load(
|
||||
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
|
||||
ElectrumWalletSnapshot? snp = null;
|
||||
|
||||
try {
|
||||
snp = await ElectrumWalletSnapshot.load(
|
||||
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
keysData =
|
||||
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||
}
|
||||
|
||||
return BitcoinCashWallet(
|
||||
mnemonic: snp.mnemonic!,
|
||||
mnemonic: keysData.mnemonic!,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses.map((addr) {
|
||||
initialAddresses: snp?.addresses.map((addr) {
|
||||
try {
|
||||
BitcoinCashAddress(addr.address);
|
||||
return BitcoinAddressRecord(
|
||||
|
@ -116,16 +133,18 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
);
|
||||
}
|
||||
}).toList(),
|
||||
initialBalance: snp.balance,
|
||||
seedBytes: await Mnemonic.toSeed(snp.mnemonic!),
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
initialBalance: snp?.balance,
|
||||
seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!),
|
||||
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||
addressPageType: P2pkhAddressType.p2pkh,
|
||||
);
|
||||
}
|
||||
|
||||
bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) =>
|
||||
bitbox.ECPair.fromWIF(hd.derive(index).wif!);
|
||||
bitbox.ECPair generateKeyPair({required Bip32Slip10Secp256k1 hd, required int index}) =>
|
||||
bitbox.ECPair.fromPrivateKey(
|
||||
Uint8List.fromList(hd.childKey(Bip32KeyIndex(index)).privateKey.raw),
|
||||
);
|
||||
|
||||
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
|
||||
int inputsCount = 0;
|
||||
|
@ -171,7 +190,11 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
|
||||
.index
|
||||
: null;
|
||||
final HD = index == null ? hd : hd.derive(index);
|
||||
return base64Encode(HD.signMessage(message));
|
||||
final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
|
||||
final priv = ECPrivate.fromWif(
|
||||
WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
|
||||
netVersion: network.wifNetVer,
|
||||
);
|
||||
return priv.signMessage(StringUtils.encode(message));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -23,6 +23,8 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
|
|||
|
||||
@override
|
||||
String getAddress(
|
||||
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
||||
{required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType}) =>
|
||||
generateP2PKHAddress(hd: hd, index: index, network: network);
|
||||
}
|
||||
|
|
|
@ -11,8 +11,11 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
|
||||
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials, BitcoinCashNewWalletCredentials> {
|
||||
class BitcoinCashWalletService extends WalletService<
|
||||
BitcoinCashNewWalletCredentials,
|
||||
BitcoinCashRestoreWalletFromSeedCredentials,
|
||||
BitcoinCashRestoreWalletFromWIFCredentials,
|
||||
BitcoinCashNewWalletCredentials> {
|
||||
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
@ -30,12 +33,14 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
|||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final wallet = await BitcoinCashWalletBase.create(
|
||||
mnemonic: await Mnemonic.generate(strength: strength),
|
||||
mnemonic: await MnemonicBip39.generate(strength: strength),
|
||||
password: credentials.password!,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
|
@ -95,7 +100,8 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
|||
|
||||
@override
|
||||
Future<BitcoinCashWallet> restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) {
|
||||
throw UnimplementedError("Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!");
|
||||
throw UnimplementedError(
|
||||
"Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!");
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
class Mnemonic {
|
||||
class MnemonicBip39 {
|
||||
/// Generate bip39 mnemonic
|
||||
static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength);
|
||||
|
||||
|
|
|
@ -21,10 +21,6 @@ dependencies:
|
|||
path: ../cw_core
|
||||
cw_bitcoin:
|
||||
path: ../cw_bitcoin
|
||||
bitcoin_flutter:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
||||
ref: cake-update-v4
|
||||
bitbox:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||
|
@ -32,11 +28,11 @@ dependencies:
|
|||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-update-v3
|
||||
ref: cake-update-v4
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v1
|
||||
ref: cake-update-v2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
@ -245,6 +245,8 @@ Future<int> getHavenCurrentHeight() async {
|
|||
|
||||
// Data taken from https://timechaincalendar.com/
|
||||
const bitcoinDates = {
|
||||
"2024-08": 854889,
|
||||
"2024-07": 850182,
|
||||
"2024-06": 846005,
|
||||
"2024-05": 841590,
|
||||
"2024-04": 837182,
|
||||
|
@ -371,7 +373,8 @@ const wowDates = {
|
|||
|
||||
int getWowneroHeightByDate({required DateTime date}) {
|
||||
String closestKey =
|
||||
wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => '');
|
||||
wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => '');
|
||||
|
||||
return wowDates[closestKey] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ Future<bool> backupWalletFilesExists(String name) async {
|
|||
backupAddressListFile.existsSync();
|
||||
}
|
||||
|
||||
// WARNING: Transaction keys and your Polyseed CANNOT be recovered if this file is deleted
|
||||
Future<void> removeCache(String name) async {
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
final cacheFile = File(path);
|
||||
|
@ -92,8 +93,8 @@ Future<void> restoreOrResetWalletFiles(String name) async {
|
|||
final backupsExists = await backupWalletFilesExists(name);
|
||||
|
||||
if (backupsExists) {
|
||||
await removeCache(name);
|
||||
|
||||
await restoreWalletFiles(name);
|
||||
}
|
||||
|
||||
removeCache(name);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ import 'package:http/io_client.dart' as ioc;
|
|||
|
||||
part 'node.g.dart';
|
||||
|
||||
Uri createUriFromElectrumAddress(String address, String path) => Uri.tryParse('tcp://$address$path')!;
|
||||
Uri createUriFromElectrumAddress(String address, String path) =>
|
||||
Uri.tryParse('tcp://$address$path')!;
|
||||
|
||||
@HiveType(typeId: Node.typeId)
|
||||
class Node extends HiveObject with Keyable {
|
||||
|
@ -72,6 +73,12 @@ class Node extends HiveObject with Keyable {
|
|||
@HiveField(7, defaultValue: '')
|
||||
String? path;
|
||||
|
||||
@HiveField(8)
|
||||
bool? isElectrs;
|
||||
|
||||
@HiveField(9)
|
||||
bool? supportsSilentPayments;
|
||||
|
||||
bool get isSSL => useSSL ?? false;
|
||||
|
||||
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
|
||||
|
|
|
@ -3,6 +3,11 @@ abstract class SyncStatus {
|
|||
double progress();
|
||||
}
|
||||
|
||||
class StartingScanSyncStatus extends SyncStatus {
|
||||
@override
|
||||
double progress() => 0.0;
|
||||
}
|
||||
|
||||
class SyncingSyncStatus extends SyncStatus {
|
||||
SyncingSyncStatus(this.blocksLeft, this.ptc);
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ abstract class TransactionInfo extends Object with Keyable {
|
|||
late TransactionDirection direction;
|
||||
late bool isPending;
|
||||
late DateTime date;
|
||||
late int height;
|
||||
int? height;
|
||||
late int confirmations;
|
||||
String amountFormatted();
|
||||
String fiatAmount();
|
||||
|
@ -25,4 +25,5 @@ abstract class TransactionInfo extends Object with Keyable {
|
|||
dynamic get keyIndex => id;
|
||||
|
||||
late Map<String, dynamic> additionalInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
110
cw_core/lib/wallet_keys_file.dart
Normal file
110
cw_core/lib/wallet_keys_file.dart
Normal file
|
@ -0,0 +1,110 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer' as dev;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/utils/file.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
mixin WalletKeysFile<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
|
||||
TransactionType extends TransactionInfo>
|
||||
on WalletBase<BalanceType, HistoryType, TransactionType> {
|
||||
Future<String> makePath() => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
// this needs to be overridden
|
||||
WalletKeysData get walletKeysData;
|
||||
|
||||
Future<String> makeKeysFilePath() async => "${await makePath()}.keys";
|
||||
|
||||
Future<void> saveKeysFile(String password, [bool isBackup = false]) async {
|
||||
try {
|
||||
final rootPath = await makeKeysFilePath();
|
||||
final path = "$rootPath${isBackup ? ".backup" : ""}";
|
||||
dev.log("Saving .keys file '$path'");
|
||||
await write(path: path, password: password, data: walletKeysData.toJSON());
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
static Future<void> createKeysFile(
|
||||
String name, WalletType type, String password, WalletKeysData walletKeysData,
|
||||
[bool withBackup = true]) async {
|
||||
try {
|
||||
final rootPath = await pathForWallet(name: name, type: type);
|
||||
final path = "$rootPath.keys";
|
||||
|
||||
dev.log("Saving .keys file '$path'");
|
||||
await write(path: path, password: password, data: walletKeysData.toJSON());
|
||||
|
||||
if (withBackup) {
|
||||
dev.log("Saving .keys.backup file '$path.backup'");
|
||||
await write(path: "$path.backup", password: password, data: walletKeysData.toJSON());
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
static Future<bool> hasKeysFile(String name, WalletType type) async {
|
||||
try {
|
||||
final path = await pathForWallet(name: name, type: type);
|
||||
return File("$path.keys").existsSync() || File("$path.keys.backup").existsSync();
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<WalletKeysData> readKeysFile(String name, WalletType type, String password) async {
|
||||
final path = await pathForWallet(name: name, type: type);
|
||||
|
||||
var readPath = "$path.keys";
|
||||
try {
|
||||
if (!File(readPath).existsSync()) throw Exception("No .keys file found for $name $type");
|
||||
|
||||
final jsonSource = await read(path: readPath, password: password);
|
||||
final data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
return WalletKeysData.fromJSON(data);
|
||||
} catch (e) {
|
||||
dev.log("Failed to read .keys file. Trying .keys.backup file...");
|
||||
|
||||
readPath = "$readPath.backup";
|
||||
if (!File(readPath).existsSync())
|
||||
throw Exception("No .keys nor a .keys.backup file found for $name $type");
|
||||
|
||||
final jsonSource = await read(path: readPath, password: password);
|
||||
final data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
final keysData = WalletKeysData.fromJSON(data);
|
||||
|
||||
dev.log("Restoring .keys from .keys.backup");
|
||||
createKeysFile(name, type, password, keysData, false);
|
||||
return keysData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WalletKeysData {
|
||||
final String? privateKey;
|
||||
final String? mnemonic;
|
||||
final String? altMnemonic;
|
||||
final String? passphrase;
|
||||
final String? xPub;
|
||||
|
||||
WalletKeysData({this.privateKey, this.mnemonic, this.altMnemonic, this.passphrase, this.xPub});
|
||||
|
||||
String toJSON() => jsonEncode({
|
||||
"privateKey": privateKey,
|
||||
"mnemonic": mnemonic,
|
||||
if (altMnemonic != null) "altMnemonic": altMnemonic,
|
||||
if (passphrase != null) "passphrase": passphrase,
|
||||
if (xPub != null) "xPub": xPub
|
||||
});
|
||||
|
||||
static WalletKeysData fromJSON(Map<String, dynamic> json) => WalletKeysData(
|
||||
privateKey: json["privateKey"] as String?,
|
||||
mnemonic: json["mnemonic"] as String?,
|
||||
altMnemonic: json["altMnemonic"] as String?,
|
||||
passphrase: json["passphrase"] as String?,
|
||||
xPub: json["xPub"] as String?,
|
||||
);
|
||||
}
|
|
@ -6,6 +6,7 @@ import 'package:cw_core/erc20_token.dart';
|
|||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
|
||||
import 'package:cw_ethereum/ethereum_client.dart';
|
||||
import 'package:cw_ethereum/ethereum_transaction_history.dart';
|
||||
|
@ -122,19 +123,37 @@ class EthereumWallet extends EVMChainWallet {
|
|||
|
||||
static Future<EthereumWallet> open(
|
||||
{required String name, required String password, required WalletInfo walletInfo}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
|
||||
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ??
|
||||
EVMChainERC20Balance(BigInt.zero);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||
}
|
||||
|
||||
return EthereumWallet(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
privateKey: privateKey,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
initialBalance: balance,
|
||||
client: EthereumClient(),
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart';
|
|||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_evm/evm_chain_client.dart';
|
||||
import 'package:cw_evm/evm_chain_exceptions.dart';
|
||||
|
@ -58,7 +59,7 @@ abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
|
|||
|
||||
abstract class EVMChainWalletBase
|
||||
extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
|
||||
with Store {
|
||||
with Store, WalletKeysFile {
|
||||
EVMChainWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
required EVMChainClient client,
|
||||
|
@ -508,6 +509,11 @@ abstract class EVMChainWalletBase
|
|||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password);
|
||||
saveKeysFile(_password, true);
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await write(path: path, password: _password, data: toJSON());
|
||||
|
@ -522,7 +528,8 @@ abstract class EVMChainWalletBase
|
|||
? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey)
|
||||
: null;
|
||||
|
||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
@override
|
||||
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
class ConnectionToNodeException implements Exception {
|
||||
ConnectionToNodeException({required this.message});
|
||||
|
||||
final String message;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class AccountRow extends Struct {
|
||||
@Int64()
|
||||
external int id;
|
||||
|
||||
external Pointer<Utf8> label;
|
||||
|
||||
String getLabel() => label.toDartString();
|
||||
int getId() => id;
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class CoinsInfoRow extends Struct {
|
||||
@Int64()
|
||||
external int blockHeight;
|
||||
|
||||
external Pointer<Utf8> hash;
|
||||
|
||||
@Uint64()
|
||||
external int internalOutputIndex;
|
||||
|
||||
@Uint64()
|
||||
external int globalOutputIndex;
|
||||
|
||||
@Int8()
|
||||
external int spent;
|
||||
|
||||
@Int8()
|
||||
external int frozen;
|
||||
|
||||
@Uint64()
|
||||
external int spentHeight;
|
||||
|
||||
@Uint64()
|
||||
external int amount;
|
||||
|
||||
@Int8()
|
||||
external int rct;
|
||||
|
||||
@Int8()
|
||||
external int keyImageKnown;
|
||||
|
||||
@Uint64()
|
||||
external int pkIndex;
|
||||
|
||||
@Uint32()
|
||||
external int subaddrIndex;
|
||||
|
||||
@Uint32()
|
||||
external int subaddrAccount;
|
||||
|
||||
external Pointer<Utf8> address;
|
||||
|
||||
external Pointer<Utf8> addressLabel;
|
||||
|
||||
external Pointer<Utf8> keyImage;
|
||||
|
||||
@Uint64()
|
||||
external int unlockTime;
|
||||
|
||||
@Int8()
|
||||
external int unlocked;
|
||||
|
||||
external Pointer<Utf8> pubKey;
|
||||
|
||||
@Int8()
|
||||
external int coinbase;
|
||||
|
||||
external Pointer<Utf8> description;
|
||||
|
||||
String getHash() => hash.toDartString();
|
||||
|
||||
String getAddress() => address.toDartString();
|
||||
|
||||
String getAddressLabel() => addressLabel.toDartString();
|
||||
|
||||
String getKeyImage() => keyImage.toDartString();
|
||||
|
||||
String getPubKey() => pubKey.toDartString();
|
||||
|
||||
String getDescription() => description.toDartString();
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class SubaddressRow extends Struct {
|
||||
@Int64()
|
||||
external int id;
|
||||
|
||||
external Pointer<Utf8> address;
|
||||
|
||||
external Pointer<Utf8> label;
|
||||
|
||||
String getLabel() => label.toDartString();
|
||||
String getAddress() => address.toDartString();
|
||||
int getId() => id;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class TransactionInfoRow extends Struct {
|
||||
@Uint64()
|
||||
external int amount;
|
||||
|
||||
@Uint64()
|
||||
external int fee;
|
||||
|
||||
@Uint64()
|
||||
external int blockHeight;
|
||||
|
||||
@Uint64()
|
||||
external int confirmations;
|
||||
|
||||
@Uint32()
|
||||
external int subaddrAccount;
|
||||
|
||||
@Int8()
|
||||
external int direction;
|
||||
|
||||
@Int8()
|
||||
external int isPending;
|
||||
|
||||
@Uint32()
|
||||
external int subaddrIndex;
|
||||
|
||||
external Pointer<Utf8> hash;
|
||||
|
||||
external Pointer<Utf8> paymentId;
|
||||
|
||||
@Int64()
|
||||
external int datetime;
|
||||
|
||||
int getDatetime() => datetime;
|
||||
int getAmount() => amount >= 0 ? amount : amount * -1;
|
||||
bool getIsPending() => isPending != 0;
|
||||
String getHash() => hash.toDartString();
|
||||
String getPaymentId() => paymentId.toDartString();
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class Utf8Box extends Struct {
|
||||
external Pointer<Utf8> value;
|
||||
|
||||
String getValue() => value.toDartString();
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'dart:ffi';
|
||||
import 'dart:isolate';
|
||||
|
||||
|
@ -288,7 +287,7 @@ class Transaction {
|
|||
};
|
||||
}
|
||||
|
||||
// S finalubAddress? subAddress;
|
||||
// final SubAddress? subAddress;
|
||||
// List<Transfer> transfers = [];
|
||||
// final int txIndex;
|
||||
final monero.TransactionInfo txInfo;
|
||||
|
@ -324,4 +323,4 @@ class Transaction {
|
|||
required this.key,
|
||||
required this.txInfo
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:cw_monero/api/account_list.dart';
|
||||
|
@ -8,8 +9,42 @@ import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart
|
|||
import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
|
||||
import 'package:cw_monero/api/transaction_history.dart';
|
||||
import 'package:cw_monero/api/wallet.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
|
||||
class MoneroCException implements Exception {
|
||||
final String message;
|
||||
|
||||
MoneroCException(this.message);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
void checkIfMoneroCIsFine() {
|
||||
final cppCsCpp = monero.MONERO_checksum_wallet2_api_c_cpp();
|
||||
final cppCsH = monero.MONERO_checksum_wallet2_api_c_h();
|
||||
final cppCsExp = monero.MONERO_checksum_wallet2_api_c_exp();
|
||||
|
||||
final dartCsCpp = monero.wallet2_api_c_cpp_sha256;
|
||||
final dartCsH = monero.wallet2_api_c_h_sha256;
|
||||
final dartCsExp = monero.wallet2_api_c_exp_sha256;
|
||||
|
||||
if (cppCsCpp != dartCsCpp) {
|
||||
throw MoneroCException("monero_c and monero.dart cpp wrapper code mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsCpp'\ndart: '$dartCsCpp'");
|
||||
}
|
||||
|
||||
if (cppCsH != dartCsH) {
|
||||
throw MoneroCException("monero_c and monero.dart cpp wrapper header mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsH'\ndart: '$dartCsH'");
|
||||
}
|
||||
|
||||
if (cppCsExp != dartCsExp && (Platform.isIOS || Platform.isMacOS)) {
|
||||
throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'");
|
||||
}
|
||||
}
|
||||
|
||||
monero.WalletManager? _wmPtr;
|
||||
final monero.WalletManager wmPtr = Pointer.fromAddress((() {
|
||||
try {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
|
||||
import 'cw_monero_platform_interface.dart';
|
||||
|
||||
class CwMonero {
|
||||
Future<String?> getPlatformVersion() {
|
||||
return CwMoneroPlatform.instance.getPlatformVersion();
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'cw_monero_platform_interface.dart';
|
||||
|
||||
/// An implementation of [CwMoneroPlatform] that uses method channels.
|
||||
class MethodChannelCwMonero extends CwMoneroPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('cw_monero');
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'cw_monero_method_channel.dart';
|
||||
|
||||
abstract class CwMoneroPlatform extends PlatformInterface {
|
||||
/// Constructs a CwMoneroPlatform.
|
||||
CwMoneroPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static CwMoneroPlatform _instance = MethodChannelCwMonero();
|
||||
|
||||
/// The default instance of [CwMoneroPlatform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelCwMonero].
|
||||
static CwMoneroPlatform get instance => _instance;
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [CwMoneroPlatform] when
|
||||
/// they register themselves.
|
||||
static set instance(CwMoneroPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<String?> getPlatformVersion() {
|
||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||
}
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/monero_amount_format.dart';
|
||||
import 'package:cw_monero/api/structs/transaction_info_row.dart';
|
||||
import 'package:cw_core/parseBoolFromString.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
|
@ -37,26 +34,6 @@ class MoneroTransactionInfo extends TransactionInfo {
|
|||
};
|
||||
}
|
||||
|
||||
MoneroTransactionInfo.fromRow(TransactionInfoRow row)
|
||||
: id = "${row.getHash()}_${row.getAmount()}_${row.subaddrAccount}_${row.subaddrIndex}",
|
||||
txHash = row.getHash(),
|
||||
height = row.blockHeight,
|
||||
direction = parseTransactionDirectionFromInt(row.direction),
|
||||
date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
|
||||
isPending = row.isPending != 0,
|
||||
amount = row.getAmount(),
|
||||
accountIndex = row.subaddrAccount,
|
||||
addressIndex = row.subaddrIndex,
|
||||
confirmations = row.confirmations,
|
||||
key = getTxKey(row.getHash()),
|
||||
fee = row.fee {
|
||||
additionalInfo = <String, dynamic>{
|
||||
'key': key,
|
||||
'accountIndex': accountIndex,
|
||||
'addressIndex': addressIndex
|
||||
};
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String txHash;
|
||||
final int height;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_monero/api/structs/coins_info_row.dart';
|
||||
|
||||
class MoneroUnspent extends Unspent {
|
||||
MoneroUnspent(
|
||||
|
@ -8,13 +7,5 @@ class MoneroUnspent extends Unspent {
|
|||
this.isFrozen = isFrozen;
|
||||
}
|
||||
|
||||
factory MoneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => MoneroUnspent(
|
||||
coinsInfoRow.getAddress(),
|
||||
coinsInfoRow.getHash(),
|
||||
coinsInfoRow.getKeyImage(),
|
||||
coinsInfoRow.amount,
|
||||
coinsInfoRow.frozen == 1,
|
||||
coinsInfoRow.unlocked == 1);
|
||||
|
||||
final bool isUnlocked;
|
||||
}
|
||||
|
|
|
@ -109,9 +109,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
@override
|
||||
String get seed => monero_wallet.getSeed();
|
||||
String seedLegacy(String? language) {
|
||||
return monero_wallet.getSeedLegacy(language);
|
||||
}
|
||||
String seedLegacy(String? language) => monero_wallet.getSeedLegacy(language);
|
||||
|
||||
@override
|
||||
MoneroWalletKeys get keys => MoneroWalletKeys(
|
||||
|
@ -190,12 +188,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
_setInitialHeight();
|
||||
_assertInitialHeight();
|
||||
} catch (_) {
|
||||
// our restore height wasn't correct, so lets see if using the backup works:
|
||||
try {
|
||||
await resetCache(name);
|
||||
_setInitialHeight();
|
||||
await resetCache(name); // Resetting the cache removes the TX Keys and Polyseed
|
||||
_assertInitialHeight();
|
||||
} catch (e) {
|
||||
// we still couldn't get a valid height from the backup?!:
|
||||
// try to use the date instead:
|
||||
|
@ -635,18 +633,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
|
||||
}
|
||||
|
||||
// check if the height is correct:
|
||||
void _setInitialHeight() {
|
||||
if (walletInfo.isRecovery) {
|
||||
return;
|
||||
}
|
||||
/// Asserts the current height to be above [MIN_RESTORE_HEIGHT]
|
||||
void _assertInitialHeight() {
|
||||
if (walletInfo.isRecovery) return;
|
||||
|
||||
final height = monero_wallet.getCurrentHeight();
|
||||
|
||||
if (height > MIN_RESTORE_HEIGHT) {
|
||||
// the restore height is probably correct, so we do nothing:
|
||||
return;
|
||||
}
|
||||
// the restore height is probably correct, so we do nothing:
|
||||
if (height > MIN_RESTORE_HEIGHT) return;
|
||||
|
||||
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,18 +21,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
|
||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.5.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039
|
||||
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.5.3"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -53,10 +53,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.4.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -69,10 +69,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
|
||||
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "4.0.1"
|
||||
build_resolvers:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -93,10 +93,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
|
||||
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.7"
|
||||
version: "7.2.10"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -109,10 +109,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725"
|
||||
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.4.3"
|
||||
version: "8.9.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -125,10 +125,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.0.3"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -141,10 +141,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
|
||||
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
version: "4.10.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -165,10 +165,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.0.3"
|
||||
cw_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -188,10 +188,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: encrypt
|
||||
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
|
||||
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
version: "5.0.3"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -204,10 +204,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -233,10 +233,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_mobx
|
||||
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
|
||||
sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.6+5"
|
||||
version: "2.2.1+1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -246,10 +246,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -262,26 +262,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
|
||||
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.1"
|
||||
hashlib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hashlib
|
||||
sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96"
|
||||
sha256: "5037d3b8c36384c03a728543ae67d962a56970c5432a50862279fe68ee4c8411"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
version: "1.19.1"
|
||||
hashlib_codecs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hashlib_codecs
|
||||
sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626"
|
||||
sha256: "2b570061f5a4b378425be28a576c1e11783450355ad4345a19f606ff3d96db0f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.5.0"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -302,10 +302,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.2.1"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -342,18 +342,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
version: "0.7.1"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -382,10 +382,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.2.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -414,33 +414,33 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5"
|
||||
mobx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobx
|
||||
sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a
|
||||
sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3+1"
|
||||
version: "2.3.3+2"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: mobx_codegen
|
||||
sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181"
|
||||
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.3.0"
|
||||
monero:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5
|
||||
resolved-ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5
|
||||
url: "https://github.com/mrcyjanek/monero.dart"
|
||||
path: "impls/monero.dart"
|
||||
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b
|
||||
resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b
|
||||
url: "https://github.com/mrcyjanek/monero_c"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
mutex:
|
||||
|
@ -451,6 +451,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -471,26 +479,26 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.3"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
||||
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.2.4"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.4.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -503,10 +511,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -519,26 +527,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
|
||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.5"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.3"
|
||||
version: "3.9.1"
|
||||
polyseed:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -555,38 +563,46 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
version: "2.1.4"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a"
|
||||
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.3.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.4.1"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
|
||||
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.4"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -596,10 +612,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: socks5_proxy
|
||||
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
|
||||
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.6"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -684,10 +700,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.2"
|
||||
unorm_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -720,22 +736,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
|
||||
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "2.4.5"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
|
||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
version: "5.5.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -748,10 +772,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.2.0-0 <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.16.6"
|
||||
|
|
|
@ -24,8 +24,9 @@ dependencies:
|
|||
path: ../cw_core
|
||||
monero:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/monero.dart
|
||||
ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5
|
||||
url: https://github.com/mrcyjanek/monero_c
|
||||
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash
|
||||
path: impls/monero.dart
|
||||
mutex: ^3.1.0
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/n2_node.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/nano_account_info_response.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
|
@ -10,23 +14,20 @@ import 'package:cw_core/pending_transaction.dart';
|
|||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_nano/file.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/n2_node.dart';
|
||||
import 'package:cw_nano/nano_balance.dart';
|
||||
import 'package:cw_nano/nano_client.dart';
|
||||
import 'package:cw_nano/nano_transaction_credentials.dart';
|
||||
import 'package:cw_nano/nano_transaction_history.dart';
|
||||
import 'package:cw_nano/nano_transaction_info.dart';
|
||||
import 'package:cw_nano/nano_wallet_addresses.dart';
|
||||
import 'package:cw_nano/nano_wallet_keys.dart';
|
||||
import 'package:cw_nano/pending_nano_transaction.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'dart:async';
|
||||
import 'package:cw_nano/nano_wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
|
||||
part 'nano_wallet.g.dart';
|
||||
|
@ -34,7 +35,8 @@ part 'nano_wallet.g.dart';
|
|||
class NanoWallet = NanoWalletBase with _$NanoWallet;
|
||||
|
||||
abstract class NanoWalletBase
|
||||
extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> with Store {
|
||||
extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo>
|
||||
with Store, WalletKeysFile {
|
||||
NanoWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
required String mnemonic,
|
||||
|
@ -70,6 +72,7 @@ abstract class NanoWalletBase
|
|||
|
||||
String? _representativeAddress;
|
||||
int repScore = 100;
|
||||
|
||||
bool get isRepOk => repScore >= 90;
|
||||
|
||||
late final NanoClient _client;
|
||||
|
@ -128,14 +131,10 @@ abstract class NanoWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||
return 0; // always 0 :)
|
||||
}
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0; // always 0 :)
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) {
|
||||
throw UnimplementedError("changePassword");
|
||||
}
|
||||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
void close() {
|
||||
|
@ -170,9 +169,7 @@ abstract class NanoWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> connectToPowNode({required Node node}) async {
|
||||
_client.connectPow(node);
|
||||
}
|
||||
Future<void> connectToPowNode({required Node node}) async => _client.connectPow(node);
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
|
@ -296,9 +293,7 @@ abstract class NanoWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
NanoWalletKeys get keys {
|
||||
return NanoWalletKeys(seedKey: _hexSeed!);
|
||||
}
|
||||
NanoWalletKeys get keys => NanoWalletKeys(seedKey: _hexSeed!);
|
||||
|
||||
@override
|
||||
String? get privateKey => _privateKey!;
|
||||
|
@ -312,6 +307,11 @@ abstract class NanoWalletBase
|
|||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password);
|
||||
saveKeysFile(_password, true);
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await write(path: path, password: _password, data: toJSON());
|
||||
|
@ -323,6 +323,9 @@ abstract class NanoWalletBase
|
|||
|
||||
String get hexSeed => _hexSeed!;
|
||||
|
||||
@override
|
||||
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, altMnemonic: hexSeed);
|
||||
|
||||
String get representative => _representativeAddress ?? "";
|
||||
|
||||
@action
|
||||
|
@ -358,8 +361,6 @@ abstract class NanoWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'seedKey': _hexSeed,
|
||||
'mnemonic': _mnemonic,
|
||||
|
@ -373,31 +374,47 @@ abstract class NanoWalletBase
|
|||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String;
|
||||
Map<String, dynamic>? data = null;
|
||||
try {
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = NanoBalance.fromRawString(
|
||||
currentBalance: data['currentBalance'] as String? ?? "0",
|
||||
receivableBalance: data['receivableBalance'] as String? ?? "0",
|
||||
currentBalance: data?['currentBalance'] as String? ?? "0",
|
||||
receivableBalance: data?['receivableBalance'] as String? ?? "0",
|
||||
);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String;
|
||||
final isHexSeed = !mnemonic.contains(' ');
|
||||
|
||||
keysData = WalletKeysData(
|
||||
mnemonic: isHexSeed ? null : mnemonic, altMnemonic: isHexSeed ? mnemonic : null);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||
}
|
||||
|
||||
DerivationType derivationType = DerivationType.nano;
|
||||
if (data['derivationType'] == "DerivationType.bip39") {
|
||||
if (data?['derivationType'] == "DerivationType.bip39") {
|
||||
derivationType = DerivationType.bip39;
|
||||
}
|
||||
|
||||
walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType);
|
||||
if (walletInfo.derivationInfo!.derivationType == null) {
|
||||
walletInfo.derivationInfo!.derivationType = derivationType;
|
||||
}
|
||||
walletInfo.derivationInfo!.derivationType ??= derivationType;
|
||||
|
||||
return NanoWallet(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
mnemonic: keysData.mnemonic!,
|
||||
initialBalance: balance,
|
||||
);
|
||||
// init() should always be run after this!
|
||||
|
@ -435,7 +452,7 @@ abstract class NanoWalletBase
|
|||
_representativeAddress = await _client.getRepFromPrefs();
|
||||
throw Exception("Failed to get representative address $e");
|
||||
}
|
||||
|
||||
|
||||
repScore = await _client.getRepScore(_representativeAddress!);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
);
|
||||
wallet.init();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,10 +29,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: b74e3842a52c61f8819a1ec8444b4de5419b41a7465e69d4aa681445377398b0
|
||||
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.5.3"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -114,7 +114,7 @@ packages:
|
|||
source: hosted
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e"
|
||||
|
@ -133,10 +133,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
|
||||
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.6.1"
|
||||
version: "8.9.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -165,10 +165,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
|
||||
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.0"
|
||||
version: "4.10.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -220,10 +220,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: ed25519_hd_key
|
||||
sha256: "326608234e986ea826a5db4cf4cd6826058d860875a3fff7926c0725fe1a604d"
|
||||
sha256: c5c9f11a03f5789bf9dcd9ae88d641571c802640851f1cacdb13123f171b3a26
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.2.1"
|
||||
encrypt:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -244,10 +244,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -475,10 +475,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: mobx
|
||||
sha256: "0afcf88b3ee9d6819890bf16c11a727fc8c62cf736fda8e5d3b9b4eace4e62ea"
|
||||
sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.3+2"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -572,10 +572,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pinenacl
|
||||
sha256: e5fb0bce1717b7f136f35ee98b5c02b3e6383211f8a77ca882fa7812232a07b9
|
||||
sha256: "3a5503637587d635647c93ea9a8fecf48a420cc7deebe6f1fc85c2a5637ab327"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4"
|
||||
version: "0.5.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -588,10 +588,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -652,10 +652,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
|
||||
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.4"
|
||||
version: "2.5.0"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -668,10 +668,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -849,18 +849,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.4"
|
||||
version: "5.5.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.4"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -870,5 +870,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.2.0-0 <4.0.0"
|
||||
flutter: ">=3.7.0"
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.16.6"
|
||||
|
|
|
@ -38,6 +38,7 @@ dev_dependencies:
|
|||
|
||||
dependency_overrides:
|
||||
watcher: ^1.1.0
|
||||
build_runner_core: 7.2.7+1
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_history.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_info.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_model.dart';
|
||||
|
@ -13,9 +14,9 @@ import 'package:cw_evm/evm_chain_wallet.dart';
|
|||
import 'package:cw_evm/evm_erc20_balance.dart';
|
||||
import 'package:cw_evm/file.dart';
|
||||
import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
|
||||
import 'package:cw_polygon/polygon_transaction_info.dart';
|
||||
import 'package:cw_polygon/polygon_client.dart';
|
||||
import 'package:cw_polygon/polygon_transaction_history.dart';
|
||||
import 'package:cw_polygon/polygon_transaction_info.dart';
|
||||
|
||||
class PolygonWallet extends EVMChainWallet {
|
||||
PolygonWallet({
|
||||
|
@ -97,19 +98,37 @@ class PolygonWallet extends EVMChainWallet {
|
|||
|
||||
static Future<PolygonWallet> open(
|
||||
{required String name, required String password, required WalletInfo walletInfo}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
|
||||
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ??
|
||||
EVMChainERC20Balance(BigInt.zero);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||
}
|
||||
|
||||
return PolygonWallet(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
privateKey: privateKey,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
initialBalance: balance,
|
||||
client: PolygonClient(),
|
||||
);
|
||||
|
|
|
@ -35,7 +35,6 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
|||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
|
@ -83,7 +82,6 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
|||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
|
|
|
@ -456,7 +456,7 @@ class SolanaWalletClient {
|
|||
funder: ownerKeypair,
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception('Insufficient SOL balance to complete this transaction');
|
||||
throw Exception('Insufficient SOL balance to complete this transaction: ${e.toString()}');
|
||||
}
|
||||
|
||||
// Input by the user
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
|
@ -12,6 +13,7 @@ import 'package:cw_core/transaction_priority.dart';
|
|||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_solana/default_spl_tokens.dart';
|
||||
import 'package:cw_solana/file.dart';
|
||||
import 'package:cw_solana/solana_balance.dart';
|
||||
|
@ -36,7 +38,8 @@ part 'solana_wallet.g.dart';
|
|||
class SolanaWallet = SolanaWalletBase with _$SolanaWallet;
|
||||
|
||||
abstract class SolanaWalletBase
|
||||
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo> with Store {
|
||||
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo>
|
||||
with Store, WalletKeysFile {
|
||||
SolanaWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
String? mnemonic,
|
||||
|
@ -121,6 +124,9 @@ abstract class SolanaWalletBase
|
|||
return privateKey;
|
||||
}
|
||||
|
||||
@override
|
||||
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
|
||||
|
||||
Future<void> init() async {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
|
||||
|
||||
|
@ -336,6 +342,11 @@ abstract class SolanaWalletBase
|
|||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password);
|
||||
saveKeysFile(_password, true);
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await write(path: path, password: _password, data: toJSON());
|
||||
|
@ -361,8 +372,6 @@ abstract class SolanaWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
'private_key': _hexPrivateKey,
|
||||
|
@ -374,18 +383,36 @@ abstract class SolanaWalletBase
|
|||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final balance = SolanaBalance.fromJSON(data['balance'] as String) ?? SolanaBalance(0.0);
|
||||
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = SolanaBalance.fromJSON(data?['balance'] as String) ?? SolanaBalance(0.0);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||
}
|
||||
|
||||
return SolanaWallet(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
privateKey: privateKey,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
initialBalance: balance,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
solana: ^0.30.1
|
||||
solana: ^0.30.4
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
http: ^1.1.0
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart';
|
|||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_tron/default_tron_tokens.dart';
|
||||
import 'package:cw_tron/file.dart';
|
||||
|
@ -37,7 +38,8 @@ part 'tron_wallet.g.dart';
|
|||
class TronWallet = TronWalletBase with _$TronWallet;
|
||||
|
||||
abstract class TronWalletBase
|
||||
extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo> with Store {
|
||||
extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo>
|
||||
with Store, WalletKeysFile {
|
||||
TronWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
String? mnemonic,
|
||||
|
@ -124,18 +126,36 @@ abstract class TronWalletBase
|
|||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final balance = TronBalance.fromJSON(data['balance'] as String) ?? TronBalance(BigInt.zero);
|
||||
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = TronBalance.fromJSON(data?['balance'] as String) ?? TronBalance(BigInt.zero);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||
}
|
||||
|
||||
return TronWallet(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
privateKey: privateKey,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
initialBalance: balance,
|
||||
);
|
||||
}
|
||||
|
@ -163,9 +183,7 @@ abstract class TronWalletBase
|
|||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (privateKey != null) {
|
||||
return TronPrivateKey(privateKey);
|
||||
}
|
||||
if (privateKey != null) return TronPrivateKey(privateKey);
|
||||
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!);
|
||||
|
||||
|
@ -181,14 +199,10 @@ abstract class TronWalletBase
|
|||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) {
|
||||
throw UnimplementedError("changePassword");
|
||||
}
|
||||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
void close() {
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
void close() => _transactionsUpdateTimer?.cancel();
|
||||
|
||||
@action
|
||||
@override
|
||||
|
@ -335,6 +349,11 @@ abstract class TronWalletBase
|
|||
continue;
|
||||
}
|
||||
|
||||
// Filter out spam transaactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions
|
||||
if (transactionModel.contracts?.first.type == "TransferAssetContract") {
|
||||
continue;
|
||||
}
|
||||
|
||||
String? tokenSymbol;
|
||||
if (transactionModel.contractAddress != null) {
|
||||
final tokenAddress = TronAddress(transactionModel.contractAddress!);
|
||||
|
@ -406,12 +425,15 @@ abstract class TronWalletBase
|
|||
Object get keys => throw UnimplementedError("keys");
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) {
|
||||
throw UnimplementedError("rescan");
|
||||
}
|
||||
Future<void> rescan({required int height}) => throw UnimplementedError("rescan");
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password);
|
||||
saveKeysFile(_password, true);
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await write(path: path, password: _password, data: toJSON());
|
||||
|
@ -424,7 +446,8 @@ abstract class TronWalletBase
|
|||
@override
|
||||
String get privateKey => _tronPrivateKey.toHex();
|
||||
|
||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
@override
|
||||
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
|
@ -512,7 +535,7 @@ abstract class TronWalletBase
|
|||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
String transactionHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
const transactionHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
|
||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
final currentWalletFile = File(currentWalletPath);
|
||||
|
@ -550,9 +573,7 @@ abstract class TronWalletBase
|
|||
Future<String> signMessage(String message, {String? address}) async =>
|
||||
_tronPrivateKey.signPersonalMessage(ascii.encode(message));
|
||||
|
||||
String getTronBase58AddressFromHex(String hexAddress) {
|
||||
return TronAddress(hexAddress).toAddress();
|
||||
}
|
||||
String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress();
|
||||
|
||||
void updateScanProviderUsageState(bool isEnabled) {
|
||||
if (isEnabled) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
@ -14,7 +15,6 @@ import 'package:cw_tron/tron_exception.dart';
|
|||
import 'package:cw_tron/tron_wallet.dart';
|
||||
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class TronWalletService extends WalletService<
|
||||
TronNewWalletCredentials,
|
||||
|
@ -153,7 +153,8 @@ class TronWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
|
||||
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>>
|
||||
restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
|
||||
// TODO: implement restoreFromHardwareWallet
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@ dependencies:
|
|||
on_chain:
|
||||
git:
|
||||
url: https://github.com/cake-tech/On_chain
|
||||
ref: cake-update-v1
|
||||
ref: cake-update-v2
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v1
|
||||
ref: cake-update-v2
|
||||
mobx: ^2.3.0+1
|
||||
bip39: ^1.0.6
|
||||
hive: ^2.2.3
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
class ConnectionToNodeException implements Exception {
|
||||
ConnectionToNodeException({required this.message});
|
||||
|
||||
final String message;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class AccountRow extends Struct {
|
||||
@Int64()
|
||||
external int id;
|
||||
|
||||
external Pointer<Utf8> label;
|
||||
|
||||
String getLabel() => label.toDartString();
|
||||
int getId() => id;
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class CoinsInfoRow extends Struct {
|
||||
@Int64()
|
||||
external int blockHeight;
|
||||
|
||||
external Pointer<Utf8> hash;
|
||||
|
||||
@Uint64()
|
||||
external int internalOutputIndex;
|
||||
|
||||
@Uint64()
|
||||
external int globalOutputIndex;
|
||||
|
||||
@Int8()
|
||||
external int spent;
|
||||
|
||||
@Int8()
|
||||
external int frozen;
|
||||
|
||||
@Uint64()
|
||||
external int spentHeight;
|
||||
|
||||
@Uint64()
|
||||
external int amount;
|
||||
|
||||
@Int8()
|
||||
external int rct;
|
||||
|
||||
@Int8()
|
||||
external int keyImageKnown;
|
||||
|
||||
@Uint64()
|
||||
external int pkIndex;
|
||||
|
||||
@Uint32()
|
||||
external int subaddrIndex;
|
||||
|
||||
@Uint32()
|
||||
external int subaddrAccount;
|
||||
|
||||
external Pointer<Utf8> address;
|
||||
|
||||
external Pointer<Utf8> addressLabel;
|
||||
|
||||
external Pointer<Utf8> keyImage;
|
||||
|
||||
@Uint64()
|
||||
external int unlockTime;
|
||||
|
||||
@Int8()
|
||||
external int unlocked;
|
||||
|
||||
external Pointer<Utf8> pubKey;
|
||||
|
||||
@Int8()
|
||||
external int coinbase;
|
||||
|
||||
external Pointer<Utf8> description;
|
||||
|
||||
String getHash() => hash.toDartString();
|
||||
|
||||
String getAddress() => address.toDartString();
|
||||
|
||||
String getAddressLabel() => addressLabel.toDartString();
|
||||
|
||||
String getKeyImage() => keyImage.toDartString();
|
||||
|
||||
String getPubKey() => pubKey.toDartString();
|
||||
|
||||
String getDescription() => description.toDartString();
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class SubaddressRow extends Struct {
|
||||
@Int64()
|
||||
external int id;
|
||||
|
||||
external Pointer<Utf8> address;
|
||||
|
||||
external Pointer<Utf8> label;
|
||||
|
||||
String getLabel() => label.toDartString();
|
||||
String getAddress() => address.toDartString();
|
||||
int getId() => id;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class TransactionInfoRow extends Struct {
|
||||
@Uint64()
|
||||
external int amount;
|
||||
|
||||
@Uint64()
|
||||
external int fee;
|
||||
|
||||
@Uint64()
|
||||
external int blockHeight;
|
||||
|
||||
@Uint64()
|
||||
external int confirmations;
|
||||
|
||||
@Uint32()
|
||||
external int subaddrAccount;
|
||||
|
||||
@Int8()
|
||||
external int direction;
|
||||
|
||||
@Int8()
|
||||
external int isPending;
|
||||
|
||||
@Uint32()
|
||||
external int subaddrIndex;
|
||||
|
||||
external Pointer<Utf8> hash;
|
||||
|
||||
external Pointer<Utf8> paymentId;
|
||||
|
||||
@Int64()
|
||||
external int datetime;
|
||||
|
||||
int getDatetime() => datetime;
|
||||
int getAmount() => amount >= 0 ? amount : amount * -1;
|
||||
bool getIsPending() => isPending != 0;
|
||||
String getHash() => hash.toDartString();
|
||||
String getPaymentId() => paymentId.toDartString();
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
class Utf8Box extends Struct {
|
||||
external Pointer<Utf8> value;
|
||||
|
||||
String getValue() => value.toDartString();
|
||||
}
|
|
@ -285,7 +285,7 @@ class Transaction {
|
|||
};
|
||||
}
|
||||
|
||||
// S finalubAddress? subAddress;
|
||||
// final SubAddress? subAddress;
|
||||
// List<Transfer> transfers = [];
|
||||
// final int txIndex;
|
||||
final wownero.TransactionInfo txInfo;
|
||||
|
@ -321,4 +321,4 @@ class Transaction {
|
|||
required this.key,
|
||||
required this.txInfo
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:cw_wownero/api/account_list.dart';
|
||||
|
@ -8,8 +9,42 @@ import 'package:cw_wownero/api/exceptions/wallet_restore_from_keys_exception.dar
|
|||
import 'package:cw_wownero/api/exceptions/wallet_restore_from_seed_exception.dart';
|
||||
import 'package:cw_wownero/api/transaction_history.dart';
|
||||
import 'package:cw_wownero/api/wallet.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:monero/wownero.dart' as wownero;
|
||||
|
||||
class MoneroCException implements Exception {
|
||||
final String message;
|
||||
|
||||
MoneroCException(this.message);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
void checkIfMoneroCIsFine() {
|
||||
final cppCsCpp = wownero.WOWNERO_checksum_wallet2_api_c_cpp();
|
||||
final cppCsH = wownero.WOWNERO_checksum_wallet2_api_c_h();
|
||||
final cppCsExp = wownero.WOWNERO_checksum_wallet2_api_c_exp();
|
||||
|
||||
final dartCsCpp = wownero.wallet2_api_c_cpp_sha256;
|
||||
final dartCsH = wownero.wallet2_api_c_h_sha256;
|
||||
final dartCsExp = wownero.wallet2_api_c_exp_sha256;
|
||||
|
||||
if (cppCsCpp != dartCsCpp) {
|
||||
throw MoneroCException("monero_c and monero.dart cpp wrapper code mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsCpp'\ndart: '$dartCsCpp'");
|
||||
}
|
||||
|
||||
if (cppCsH != dartCsH) {
|
||||
throw MoneroCException("monero_c and monero.dart cpp wrapper header mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsH'\ndart: '$dartCsH'");
|
||||
}
|
||||
|
||||
if (cppCsExp != dartCsExp && (Platform.isIOS || Platform.isMacOS)) {
|
||||
throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'");
|
||||
}
|
||||
}
|
||||
|
||||
wownero.WalletManager? _wmPtr;
|
||||
final wownero.WalletManager wmPtr = Pointer.fromAddress((() {
|
||||
try {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
|
||||
import 'cw_wownero_platform_interface.dart';
|
||||
|
||||
class CwWownero {
|
||||
Future<String?> getPlatformVersion() {
|
||||
return CwWowneroPlatform.instance.getPlatformVersion();
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'cw_wownero_platform_interface.dart';
|
||||
|
||||
/// An implementation of [CwWowneroPlatform] that uses method channels.
|
||||
class MethodChannelCwWownero extends CwWowneroPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('cw_wownero');
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'cw_wownero_method_channel.dart';
|
||||
|
||||
abstract class CwWowneroPlatform extends PlatformInterface {
|
||||
/// Constructs a CwWowneroPlatform.
|
||||
CwWowneroPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static CwWowneroPlatform _instance = MethodChannelCwWownero();
|
||||
|
||||
/// The default instance of [CwWowneroPlatform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelCwWownero].
|
||||
static CwWowneroPlatform get instance => _instance;
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [CwWowneroPlatform] when
|
||||
/// they register themselves.
|
||||
static set instance(CwWowneroPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<String?> getPlatformVersion() {
|
||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,5 @@
|
|||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wownero_amount_format.dart';
|
||||
import 'package:cw_wownero/api/structs/transaction_info_row.dart';
|
||||
import 'package:cw_core/parseBoolFromString.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
|
@ -35,26 +34,6 @@ class WowneroTransactionInfo extends TransactionInfo {
|
|||
};
|
||||
}
|
||||
|
||||
WowneroTransactionInfo.fromRow(TransactionInfoRow row)
|
||||
: id = "${row.getHash()}_${row.getAmount()}_${row.subaddrAccount}_${row.subaddrIndex}",
|
||||
txHash = row.getHash(),
|
||||
height = row.blockHeight,
|
||||
direction = parseTransactionDirectionFromInt(row.direction),
|
||||
date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
|
||||
isPending = row.isPending != 0,
|
||||
amount = row.getAmount(),
|
||||
accountIndex = row.subaddrAccount,
|
||||
addressIndex = row.subaddrIndex,
|
||||
confirmations = row.confirmations,
|
||||
key = getTxKey(row.getHash()),
|
||||
fee = row.fee {
|
||||
additionalInfo = <String, dynamic>{
|
||||
'key': key,
|
||||
'accountIndex': accountIndex,
|
||||
'addressIndex': addressIndex
|
||||
};
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String txHash;
|
||||
final int height;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_wownero/api/structs/coins_info_row.dart';
|
||||
|
||||
class WowneroUnspent extends Unspent {
|
||||
WowneroUnspent(
|
||||
|
@ -8,13 +7,5 @@ class WowneroUnspent extends Unspent {
|
|||
this.isFrozen = isFrozen;
|
||||
}
|
||||
|
||||
factory WowneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => WowneroUnspent(
|
||||
coinsInfoRow.getAddress(),
|
||||
coinsInfoRow.getHash(),
|
||||
coinsInfoRow.getKeyImage(),
|
||||
coinsInfoRow.amount,
|
||||
coinsInfoRow.frozen == 1,
|
||||
coinsInfoRow.unlocked == 1);
|
||||
|
||||
final bool isUnlocked;
|
||||
}
|
||||
|
|
|
@ -107,9 +107,7 @@ abstract class WowneroWalletBase
|
|||
@override
|
||||
String get seed => wownero_wallet.getSeed();
|
||||
|
||||
String seedLegacy(String? language) {
|
||||
return wownero_wallet.getSeedLegacy(language);
|
||||
}
|
||||
String seedLegacy(String? language) => wownero_wallet.getSeedLegacy(language);
|
||||
|
||||
@override
|
||||
MoneroWalletKeys get keys => MoneroWalletKeys(
|
||||
|
@ -182,12 +180,12 @@ abstract class WowneroWalletBase
|
|||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
_setInitialHeight();
|
||||
_assertInitialHeight();
|
||||
} catch (_) {
|
||||
// our restore height wasn't correct, so lets see if using the backup works:
|
||||
try {
|
||||
await resetCache(name);
|
||||
_setInitialHeight();
|
||||
_assertInitialHeight();
|
||||
} catch (e) {
|
||||
// we still couldn't get a valid height from the backup?!:
|
||||
// try to use the date instead:
|
||||
|
@ -604,18 +602,14 @@ abstract class WowneroWalletBase
|
|||
_listener = wownero_wallet.setListeners(_onNewBlock, _onNewTransaction);
|
||||
}
|
||||
|
||||
// check if the height is correct:
|
||||
void _setInitialHeight() {
|
||||
if (walletInfo.isRecovery) {
|
||||
return;
|
||||
}
|
||||
/// Asserts the current height to be above [MIN_RESTORE_HEIGHT]
|
||||
void _assertInitialHeight() {
|
||||
if (walletInfo.isRecovery) return;
|
||||
|
||||
final height = wownero_wallet.getCurrentHeight();
|
||||
|
||||
if (height > MIN_RESTORE_HEIGHT) {
|
||||
// the restore height is probably correct, so we do nothing:
|
||||
return;
|
||||
}
|
||||
// the restore height is probably correct, so we do nothing:
|
||||
if (height > MIN_RESTORE_HEIGHT) return;
|
||||
|
||||
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
|
||||
}
|
||||
|
|
|
@ -437,10 +437,10 @@ packages:
|
|||
monero:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5
|
||||
resolved-ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5
|
||||
url: "https://github.com/mrcyjanek/monero.dart"
|
||||
path: "impls/monero.dart"
|
||||
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b
|
||||
resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b
|
||||
url: "https://github.com/mrcyjanek/monero_c"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
mutex:
|
||||
|
|
|
@ -24,8 +24,9 @@ dependencies:
|
|||
path: ../cw_core
|
||||
monero:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/monero.dart
|
||||
ref: d46753eca865e9e56c2f0ef6fe485c42e11982c5
|
||||
url: https://github.com/mrcyjanek/monero_c
|
||||
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash
|
||||
path: impls/monero.dart
|
||||
mutex: ^3.1.0
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
@ -238,7 +238,7 @@ SPEC CHECKSUMS:
|
|||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||
Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2
|
||||
|
@ -246,7 +246,7 @@ SPEC CHECKSUMS:
|
|||
reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c
|
||||
SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d
|
||||
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
|
||||
SwiftProtobuf: 5e8349171e7c2f88f5b9e683cb3cb79d1dc780b3
|
||||
|
@ -255,7 +255,7 @@ SPEC CHECKSUMS:
|
|||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
||||
|
||||
PODFILE CHECKSUM: a2fe518be61cdbdc5b0e2da085ab543d556af2d3
|
||||
|
|
|
@ -20,7 +20,7 @@ class AnonPayApi {
|
|||
final WalletBase wallet;
|
||||
|
||||
static const anonpayRef = secrets.anonPayReferralCode;
|
||||
static const onionApiAuthority = 'trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion';
|
||||
static const onionApiAuthority = 'tqzngtf2hybjbexznel6dhgsvbynjzezoybvtv6iofomx7gchqfssgqd.onion';
|
||||
static const clearNetAuthority = 'trocador.app';
|
||||
static const markup = secrets.trocadorExchangeMarkup;
|
||||
static const anonPayPath = '/anonpay';
|
||||
|
|
9
lib/app_scroll_behavior.dart
Normal file
9
lib/app_scroll_behavior.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppScrollBehavior extends MaterialScrollBehavior {
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices =>
|
||||
{PointerDeviceKind.touch, PointerDeviceKind.mouse, PointerDeviceKind.trackpad};
|
||||
}
|
|
@ -302,16 +302,13 @@ class CWBitcoin extends Bitcoin {
|
|||
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
||||
|
||||
late BasedUtxoNetwork network;
|
||||
btc.NetworkType networkType;
|
||||
switch (node.type) {
|
||||
case WalletType.litecoin:
|
||||
network = LitecoinNetwork.mainnet;
|
||||
networkType = litecoinNetwork;
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
default:
|
||||
network = BitcoinNetwork.mainnet;
|
||||
networkType = btc.bitcoin;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -341,10 +338,8 @@ class CWBitcoin extends Bitcoin {
|
|||
balancePath += "/0";
|
||||
}
|
||||
|
||||
final hd = btc.HDWallet.fromSeed(
|
||||
seedBytes,
|
||||
network: networkType,
|
||||
).derivePath(balancePath);
|
||||
final hd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(balancePath)
|
||||
as Bip32Slip10Secp256k1;
|
||||
|
||||
// derive address at index 0:
|
||||
String? address;
|
||||
|
@ -435,6 +430,13 @@ class CWBitcoin extends Bitcoin {
|
|||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int feeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount,
|
||||
{int? size}) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.feeAmountWithFeeRate(feeRate, inputsCount, outputsCount, size: size);
|
||||
}
|
||||
|
||||
@override
|
||||
int getMaxCustomFeeRate(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
|
@ -508,10 +510,7 @@ class CWBitcoin extends Bitcoin {
|
|||
@override
|
||||
Future<void> setScanningActive(Object wallet, bool active) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
bitcoinWallet.setSilentPaymentsScanning(
|
||||
active,
|
||||
active && (await getNodeIsElectrsSPEnabled(wallet)),
|
||||
);
|
||||
bitcoinWallet.setSilentPaymentsScanning(active);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -529,44 +528,10 @@ class CWBitcoin extends Bitcoin {
|
|||
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
|
||||
}
|
||||
|
||||
Future<bool> getNodeIsElectrs(Object wallet) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
|
||||
final version = await bitcoinWallet.electrumClient.version();
|
||||
|
||||
if (version.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final server = version[0];
|
||||
|
||||
if (server.toLowerCase().contains('electrs')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
|
||||
if (!(await getNodeIsElectrs(wallet))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
try {
|
||||
final tweaksResponse = await bitcoinWallet.electrumClient.getTweaks(height: 0);
|
||||
|
||||
if (tweaksResponse != null) {
|
||||
return true;
|
||||
}
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
return false;
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return false;
|
||||
return bitcoinWallet.getNodeSupportsSilentPayments();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -52,5 +52,9 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
|||
return S.current.sync_status_syncronizing;
|
||||
}
|
||||
|
||||
if (syncStatus is StartingScanSyncStatus) {
|
||||
return S.current.sync_status_starting_scan;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -759,8 +759,8 @@ Future<void> setup({
|
|||
getIt.registerFactory(() => TrocadorProvidersViewModel(getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return OtherSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||
});
|
||||
return OtherSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!,
|
||||
getIt.get<SendViewModel>());});
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return SecuritySettingsViewModel(getIt.get<SettingsStore>());
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import 'package:cake_wallet/app_scroll_behavior.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
|
@ -75,6 +76,7 @@ Future<void> main({Key? topLevelKey}) async {
|
|||
runApp(
|
||||
MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior: AppScrollBehavior(),
|
||||
home: Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
|
@ -300,6 +302,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
|||
locale: Locale(settingsStore.languageCode),
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
initialRoute: initialRoute,
|
||||
scrollBehavior: AppScrollBehavior(),
|
||||
home: _Home(),
|
||||
));
|
||||
});
|
||||
|
|
|
@ -346,4 +346,9 @@ class CWMonero extends Monero {
|
|||
Future<int> getCurrentHeight() async {
|
||||
return monero_wallet_api.getCurrentHeight();
|
||||
}
|
||||
|
||||
@override
|
||||
void monerocCheck() {
|
||||
checkIfMoneroCIsFine();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget? middle(BuildContext context) {
|
||||
return Text(
|
||||
return Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
|
@ -359,7 +359,7 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
reaction((_) => cakePayPurchaseViewModel.sendViewModel.state, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showStateAlert(context, S.of(context).error, state.error);
|
||||
if (context.mounted) showStateAlert(context, S.of(context).error, state.error);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -381,31 +381,35 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
}
|
||||
|
||||
void showStateAlert(BuildContext context, String title, String content) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: title,
|
||||
alertContent: content,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
if (context.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: title,
|
||||
alertContent: content,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showSentAlert(BuildContext context) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
final order = cakePayPurchaseViewModel.order!.orderId;
|
||||
final isCopy = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).transaction_sent,
|
||||
alertContent:
|
||||
S.of(context).cake_pay_save_order + '\n${order}',
|
||||
leftButtonText: S.of(context).ignor,
|
||||
rightButtonText: S.of(context).copy,
|
||||
actionLeftButton: () => Navigator.of(context).pop(false),
|
||||
actionRightButton: () => Navigator.of(context).pop(true));
|
||||
}) ??
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).transaction_sent,
|
||||
alertContent: S.of(context).cake_pay_save_order + '\n${order}',
|
||||
leftButtonText: S.of(context).ignor,
|
||||
rightButtonText: S.of(context).copy,
|
||||
actionLeftButton: () => Navigator.of(context).pop(false),
|
||||
actionRightButton: () => Navigator.of(context).pop(true));
|
||||
}) ??
|
||||
false;
|
||||
|
||||
if (isCopy) {
|
||||
|
|
|
@ -241,7 +241,11 @@ class _DashboardPageView extends BasePage {
|
|||
padding: EdgeInsets.only(bottom: 24, top: 10),
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return ExcludeSemantics(
|
||||
return Semantics(
|
||||
button: false,
|
||||
label: 'Page Indicator',
|
||||
hint: 'Swipe to change page',
|
||||
excludeSemantics: true,
|
||||
child: SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: pages.length,
|
||||
|
|
|
@ -23,6 +23,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
|||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
@ -124,6 +125,36 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (dashboardViewModel.getMoneroError != null) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16,0,16,16),
|
||||
child: DashBoardRoundedCardWidget(
|
||||
title: "Invalid monero bindings",
|
||||
subTitle: dashboardViewModel.getMoneroError.toString(),
|
||||
onTap: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (dashboardViewModel.getWowneroError != null) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16,0,16,16),
|
||||
child: DashBoardRoundedCardWidget(
|
||||
title: "Invalid wownero bindings",
|
||||
subTitle: dashboardViewModel.getWowneroError.toString(),
|
||||
onTap: () {},
|
||||
)
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => dashboardViewModel.balanceViewModel.hasAccounts
|
||||
? HomeScreenAccountWidget(
|
||||
|
@ -439,15 +470,19 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('${availableBalanceLabel}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
height: 1)),
|
||||
Semantics(
|
||||
hint: 'Double tap to see more information',
|
||||
container: true,
|
||||
child: Text('${availableBalanceLabel}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
height: 1)),
|
||||
),
|
||||
if (hasAdditionalBalance)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
|
|
|
@ -67,17 +67,6 @@ class ExchangePage extends BasePage {
|
|||
Debounce _depositAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
var _isReactionsSet = false;
|
||||
|
||||
final arrowBottomPurple = Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
);
|
||||
final arrowBottomCakeGreen = Image.asset(
|
||||
'assets/images/arrow_bottom_cake_green.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
);
|
||||
|
||||
late final String? depositWalletName;
|
||||
late final String? receiveWalletName;
|
||||
|
||||
|
@ -666,7 +655,6 @@ class ExchangePage extends BasePage {
|
|||
|
||||
exchangeViewModel.changeDepositCurrency(currency: currency);
|
||||
},
|
||||
imageArrow: arrowBottomPurple,
|
||||
currencyButtonColor: Colors.transparent,
|
||||
addressButtonsColor:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
|
@ -714,7 +702,6 @@ class ExchangePage extends BasePage {
|
|||
currencies: exchangeViewModel.receiveCurrencies,
|
||||
onCurrencySelected: (currency) =>
|
||||
exchangeViewModel.changeReceiveCurrency(currency: currency),
|
||||
imageArrow: arrowBottomCakeGreen,
|
||||
currencyButtonColor: Colors.transparent,
|
||||
addressButtonsColor:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:cake_wallet/core/amount_validator.dart';
|
||||
import 'package:cake_wallet/entities/contact_base.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
|
||||
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
|
@ -27,7 +28,7 @@ class ExchangeCard extends StatefulWidget {
|
|||
required this.isAmountEstimated,
|
||||
required this.currencies,
|
||||
required this.onCurrencySelected,
|
||||
required this.imageArrow,
|
||||
this.imageArrow,
|
||||
this.currencyValueValidator,
|
||||
this.addressTextFieldValidator,
|
||||
this.title = '',
|
||||
|
@ -59,7 +60,7 @@ class ExchangeCard extends StatefulWidget {
|
|||
final bool isAmountEstimated;
|
||||
final bool hasRefundAddress;
|
||||
final bool isMoneroWallet;
|
||||
final Image imageArrow;
|
||||
final Image? imageArrow;
|
||||
final Color currencyButtonColor;
|
||||
final Color? addressButtonsColor;
|
||||
final Color borderColor;
|
||||
|
@ -197,127 +198,18 @@ class ExchangeCardState extends State<ExchangeCard> {
|
|||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
height: 32,
|
||||
color: widget.currencyButtonColor,
|
||||
child: InkWell(
|
||||
key: ValueKey('${_cardInstanceName}_currency_picker_button_key'),
|
||||
onTap: () => _presentPicker(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: widget.imageArrow,
|
||||
),
|
||||
Text(
|
||||
key: ValueKey('${_cardInstanceName}_selected_currency_text_key'),
|
||||
_selectedCurrency.toString(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white),
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
if (_selectedCurrency.tag != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 3.0),
|
||||
child: Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.addressButtonsColor ??
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
key: ValueKey('${_cardInstanceName}_selected_currency_tag_text_key'),
|
||||
_selectedCurrency.tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonIconColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: Text(':',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FocusTraversalOrder(
|
||||
order: NumericFocusOrder(1),
|
||||
child: BaseTextFormField(
|
||||
key: ValueKey('${_cardInstanceName}_amount_textfield_key'),
|
||||
focusNode: widget.amountFocusNode,
|
||||
controller: amountController,
|
||||
enabled: _isAmountEditable,
|
||||
textAlign: TextAlign.left,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
hintText: '0.0000',
|
||||
borderColor: Colors.transparent,
|
||||
//widget.borderColor,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.extension<ExchangePageTheme>()!
|
||||
.hintTextColor),
|
||||
validator: _isAmountEditable ? widget.currencyValueValidator : null),
|
||||
),
|
||||
),
|
||||
if (widget.hasAllAmount)
|
||||
Container(
|
||||
height: 32,
|
||||
width: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||
child: InkWell(
|
||||
key: ValueKey('${_cardInstanceName}_send_all_button_key'),
|
||||
onTap: () => widget.allAmount?.call(),
|
||||
child: Center(
|
||||
child: Text(S.of(context).all,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonIconColor)),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
CurrencyAmountTextField(
|
||||
imageArrow: widget.imageArrow,
|
||||
selectedCurrency: _selectedCurrency.toString(),
|
||||
amountFocusNode: widget.amountFocusNode,
|
||||
amountController: amountController,
|
||||
onTapPicker: () => _presentPicker(context),
|
||||
isAmountEditable: _isAmountEditable,
|
||||
isPickerEnable: true,
|
||||
allAmountButton: widget.hasAllAmount,
|
||||
currencyValueValidator: widget.currencyValueValidator,
|
||||
tag: _selectedCurrency.tag,
|
||||
allAmountCallback: widget.allAmount),
|
||||
Divider(height: 1, color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
|
|
|
@ -40,11 +40,11 @@ class NewWalletPage extends BasePage {
|
|||
|
||||
@override
|
||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.focusedChild?.unfocus();
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => WalletNameForm(
|
||||
|
@ -88,15 +88,17 @@ class _WalletNameFormState extends State<WalletNameForm> {
|
|||
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.new_wallet,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
if (context.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.new_wallet,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,135 +1,210 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
|
||||
class CurrencyInputField extends StatelessWidget {
|
||||
const CurrencyInputField({
|
||||
super.key,
|
||||
required this.onTapPicker,
|
||||
class CurrencyAmountTextField extends StatelessWidget {
|
||||
const CurrencyAmountTextField({
|
||||
required this.selectedCurrency,
|
||||
this.focusNode,
|
||||
required this.controller,
|
||||
required this.isLight,
|
||||
required this.amountFocusNode,
|
||||
required this.amountController,
|
||||
required this.isAmountEditable,
|
||||
this.allAmountButton = false,
|
||||
this.isPickerEnable = false,
|
||||
this.isSelected = false,
|
||||
this.currentTheme = ThemeType.dark,
|
||||
this.onTapPicker,
|
||||
this.padding,
|
||||
this.imageArrow,
|
||||
this.hintText,
|
||||
this.tag,
|
||||
this.tagBackgroundColor,
|
||||
this.currencyValueValidator,
|
||||
this.allAmountCallback,
|
||||
});
|
||||
|
||||
final Function() onTapPicker;
|
||||
final Currency selectedCurrency;
|
||||
final FocusNode? focusNode;
|
||||
final TextEditingController controller;
|
||||
final bool isLight;
|
||||
|
||||
String get _currencyName {
|
||||
if (selectedCurrency is CryptoCurrency) {
|
||||
return (selectedCurrency as CryptoCurrency).title.toUpperCase();
|
||||
}
|
||||
return selectedCurrency.name.toUpperCase();
|
||||
}
|
||||
final Widget? imageArrow;
|
||||
final String selectedCurrency;
|
||||
final String? tag;
|
||||
final String? hintText;
|
||||
final Color? tagBackgroundColor;
|
||||
final EdgeInsets? padding;
|
||||
final FocusNode? amountFocusNode;
|
||||
final TextEditingController amountController;
|
||||
final bool isAmountEditable;
|
||||
final FormFieldValidator<String>? currencyValueValidator;
|
||||
final bool isPickerEnable;
|
||||
final ThemeType currentTheme;
|
||||
final bool isSelected;
|
||||
final bool allAmountButton;
|
||||
final VoidCallback? allAmountCallback;
|
||||
final VoidCallback? onTapPicker;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final arrowBottomPurple = Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
height: 8,
|
||||
);
|
||||
// This magic number for wider screen sets the text input focus at center of the inputfield
|
||||
final _width =
|
||||
responsiveLayoutUtil.shouldRenderMobileUI ? MediaQuery.of(context).size.width : 500;
|
||||
|
||||
return Column(
|
||||
final textColor = currentTheme == ThemeType.light
|
||||
? Theme.of(context).appBarTheme.titleTextStyle!.color!
|
||||
: Colors.white;
|
||||
final _prefixContent = Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: BaseTextFormField(
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+(\.|\,)?\d{0,8}'))],
|
||||
hintText: '0.000',
|
||||
placeholderTextStyle: isLight
|
||||
? null
|
||||
: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
borderColor: Theme.of(context).extension<PickerTheme>()!.dividerColor,
|
||||
textColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
textStyle: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
),
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: _width / 4,
|
||||
isPickerEnable
|
||||
? Container(
|
||||
height: 32,
|
||||
child: InkWell(
|
||||
onTap: onTapPicker,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: imageArrow ??
|
||||
Image.asset('assets/images/arrow_bottom_purple_icon.png',
|
||||
color: textColor, height: 8)),
|
||||
Text(
|
||||
selectedCurrency,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: InkWell(
|
||||
onTap: onTapPicker,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: arrowBottomPurple,
|
||||
),
|
||||
Text(
|
||||
_currencyName,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
),
|
||||
),
|
||||
if (selectedCurrency.tag != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
selectedCurrency.tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 3.0),
|
||||
child: Text(
|
||||
':',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 20,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
)
|
||||
: Text(
|
||||
selectedCurrency,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
if (tag != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3.0),
|
||||
child: Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: tagBackgroundColor ??
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 4.0),
|
||||
child: Text(
|
||||
':',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
isSelected
|
||||
? Container(
|
||||
child: _prefixContent,
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
margin: const EdgeInsets.only(right: 3),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: textColor,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
color: Theme.of(context).primaryColor))
|
||||
: _prefixContent,
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FocusTraversalOrder(
|
||||
order: NumericFocusOrder(1),
|
||||
child: BaseTextFormField(
|
||||
focusNode: amountFocusNode,
|
||||
controller: amountController,
|
||||
enabled: isAmountEditable,
|
||||
textAlign: TextAlign.left,
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
signed: false,
|
||||
decimal: true,
|
||||
),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')),
|
||||
],
|
||||
hintText: hintText ?? '0.0000',
|
||||
borderColor: Colors.transparent,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
),
|
||||
placeholderTextStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: currentTheme == ThemeType.light
|
||||
? Theme.of(context).appBarTheme.titleTextStyle!.color!
|
||||
: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
|
||||
),
|
||||
validator: isAmountEditable ? currencyValueValidator : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (allAmountButton)
|
||||
Container(
|
||||
height: 32,
|
||||
width: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: allAmountCallback,
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).all,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonIconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/brightness_util.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
@ -38,6 +42,10 @@ class QRWidget extends StatelessWidget {
|
|||
final copyImage = Image.asset('assets/images/copy_address.png',
|
||||
color: Theme.of(context).extension<QRCodeTheme>()!.qrWidgetCopyButtonColor);
|
||||
|
||||
// This magic number for wider screen sets the text input focus at center of the inputfield
|
||||
final _width =
|
||||
responsiveLayoutUtil.shouldRenderMobileUI ? MediaQuery.of(context).size.width : 500;
|
||||
|
||||
return Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
|
@ -85,8 +93,9 @@ class QRWidget extends StatelessWidget {
|
|||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 3,
|
||||
color:
|
||||
Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
color: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.textColor,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
|
@ -116,20 +125,23 @@ class QRWidget extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: CurrencyInputField(
|
||||
focusNode: amountTextFieldFocusNode,
|
||||
controller: amountController,
|
||||
onTapPicker: () => _presentPicker(context),
|
||||
selectedCurrency: addressListViewModel.selectedCurrency,
|
||||
isLight: isLight,
|
||||
),
|
||||
),
|
||||
key: formKey,
|
||||
child: CurrencyAmountTextField(
|
||||
selectedCurrency: _currencyName,
|
||||
amountFocusNode: amountTextFieldFocusNode,
|
||||
amountController: amountController,
|
||||
padding: EdgeInsets.only(top: 20, left: _width / 4),
|
||||
currentTheme: isLight ? ThemeType.light : ThemeType.dark,
|
||||
isAmountEditable: true,
|
||||
tag: addressListViewModel.selectedCurrency.tag,
|
||||
onTapPicker: () => _presentPicker(context),
|
||||
isPickerEnable: true)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
Divider(height: 1, color: Theme.of(context).extension<PickerTheme>()!.dividerColor),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20, bottom: 8),
|
||||
child: Builder(
|
||||
|
@ -150,7 +162,8 @@ class QRWidget extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor),
|
||||
color:
|
||||
Theme.of(context).extension<DashboardPageTheme>()!.textColor),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
|
@ -169,6 +182,13 @@ class QRWidget extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
String get _currencyName {
|
||||
if (addressListViewModel.selectedCurrency is CryptoCurrency) {
|
||||
return (addressListViewModel.selectedCurrency as CryptoCurrency).title.toUpperCase();
|
||||
}
|
||||
return addressListViewModel.selectedCurrency.name.toUpperCase();
|
||||
}
|
||||
|
||||
void _presentPicker(BuildContext context) async {
|
||||
await showPopUp<void>(
|
||||
builder: (_) => CurrencyPicker(
|
||||
|
|
|
@ -177,16 +177,22 @@ class WalletRestorePage extends BasePage {
|
|||
if (_pages.length > 1)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SmoothPageIndicator(
|
||||
controller: _controller,
|
||||
count: _pages.length,
|
||||
effect: ColorTransitionEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context).hintColor.withOpacity(0.5),
|
||||
activeDotColor: Theme.of(context).hintColor,
|
||||
child: Semantics(
|
||||
button: false,
|
||||
label: 'Page Indicator',
|
||||
hint: 'Swipe to change restore mode',
|
||||
excludeSemantics: true,
|
||||
child: SmoothPageIndicator(
|
||||
controller: _controller,
|
||||
count: _pages.length,
|
||||
effect: ColorTransitionEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context).hintColor.withOpacity(0.5),
|
||||
activeDotColor: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -212,7 +212,12 @@ class SendPage extends BasePage {
|
|||
final count = sendViewModel.outputs.length;
|
||||
|
||||
return count > 1
|
||||
? SmoothPageIndicator(
|
||||
? Semantics (
|
||||
label: 'Page Indicator',
|
||||
hint: 'Swipe to change receiver',
|
||||
excludeSemantics: true,
|
||||
child:
|
||||
SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: count,
|
||||
effect: ScrollingDotsEffect(
|
||||
|
@ -226,7 +231,7 @@ class SendPage extends BasePage {
|
|||
activeDotColor: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.templateBackgroundColor),
|
||||
)
|
||||
))
|
||||
: Offstage();
|
||||
},
|
||||
),
|
||||
|
|
|
@ -94,21 +94,27 @@ class SendTemplatePage extends BasePage {
|
|||
final count = sendTemplateViewModel.recipients.length;
|
||||
|
||||
return count > 1
|
||||
? SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: count,
|
||||
effect: ScrollingDotsEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.indicatorDotColor,
|
||||
activeDotColor: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.indicatorDotTheme
|
||||
.activeIndicatorColor))
|
||||
? Semantics(
|
||||
button: false,
|
||||
label: 'Page Indicator',
|
||||
hint: 'Swipe to change receiver',
|
||||
excludeSemantics: true,
|
||||
child: SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: count,
|
||||
effect: ScrollingDotsEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.indicatorDotColor,
|
||||
activeDotColor: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.indicatorDotTheme
|
||||
.activeIndicatorColor)),
|
||||
)
|
||||
: Offstage();
|
||||
},
|
||||
),
|
||||
|
@ -144,7 +150,7 @@ class SendTemplatePage extends BasePage {
|
|||
.toList();
|
||||
|
||||
sendTemplateViewModel.addTemplate(
|
||||
isCurrencySelected: mainTemplate.isCurrencySelected,
|
||||
isCurrencySelected: mainTemplate.isCryptoSelected,
|
||||
name: mainTemplate.name,
|
||||
address: mainTemplate.address,
|
||||
cryptoCurrency: mainTemplate.selectedCurrency.title,
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PrefixCurrencyIcon extends StatelessWidget {
|
||||
PrefixCurrencyIcon({
|
||||
required this.isSelected,
|
||||
required this.title,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final bool isSelected;
|
||||
final String title;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
color: isSelected
|
||||
? Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.templateSelectedCurrencyBackgroundColor
|
||||
: Colors.transparent,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (onTap != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected
|
||||
? Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.templateSelectedCurrencyTitleColor
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
|
@ -208,169 +209,19 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
textStyle: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
validator: sendViewModel.addressValidator)),
|
||||
Observer(
|
||||
builder: (_) => Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
sendViewModel.hasMultipleTokens
|
||||
? Container(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
height: 32,
|
||||
child: InkWell(
|
||||
key: ValueKey('send_page_currency_picker_button_key'),
|
||||
onTap: () => _presentPicker(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: Image.asset(
|
||||
'assets/images/arrow_bottom_purple_icon.png',
|
||||
color: Colors.white,
|
||||
height: 8,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white),
|
||||
),
|
||||
sendViewModel.selectedCryptoCurrency.tag != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.fromLTRB(3.0, 0, 3.0, 0),
|
||||
child: Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonColor,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
)),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
sendViewModel.selectedCryptoCurrency.tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonIconColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
':',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
key: ValueKey('send_page_amount_textfield_key'),
|
||||
focusNode: cryptoAmountFocus,
|
||||
controller: cryptoAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
],
|
||||
suffixIcon: SizedBox(
|
||||
width: prefixIconWidth,
|
||||
),
|
||||
hintText: '0.0000',
|
||||
borderColor: Colors.transparent,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldHintColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: output.sendAll
|
||||
? sendViewModel.allAmountValidator
|
||||
: sendViewModel.amountValidator,
|
||||
),
|
||||
if (!sendViewModel.isBatchSending)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: prefixIconWidth,
|
||||
height: prefixIconHeight,
|
||||
child: InkWell(
|
||||
key: ValueKey('send_page_send_all_button_key'),
|
||||
onTap: () async {
|
||||
output.setSendAll(sendViewModel.balance);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonColor,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).all,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonIconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
CurrencyAmountTextField(
|
||||
selectedCurrency: sendViewModel.selectedCryptoCurrency.title,
|
||||
amountFocusNode: cryptoAmountFocus,
|
||||
amountController: cryptoAmountController,
|
||||
isAmountEditable: true,
|
||||
onTapPicker: () => _presentPicker(context),
|
||||
isPickerEnable: sendViewModel.hasMultipleTokens,
|
||||
tag: sendViewModel.selectedCryptoCurrency.tag,
|
||||
allAmountButton: !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
|
||||
currencyValueValidator: output.sendAll
|
||||
? sendViewModel.allAmountValidator
|
||||
: sendViewModel.amountValidator,
|
||||
allAmountCallback: () async => output.setSendAll(sendViewModel.balance)),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
|
||||
|
@ -406,42 +257,16 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
),
|
||||
),
|
||||
if (!sendViewModel.isFiatDisabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
key: ValueKey('send_page_fiat_amount_textfield_key'),
|
||||
focusNode: fiatAmountFocus,
|
||||
controller: fiatAmountController,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(
|
||||
RegExp('[\\-|\\ ]'),
|
||||
)
|
||||
],
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.only(top: 9),
|
||||
child: Text(
|
||||
sendViewModel.fiat.title + ':',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
CurrencyAmountTextField(
|
||||
selectedCurrency: sendViewModel.fiat.title,
|
||||
amountFocusNode: fiatAmountFocus,
|
||||
amountController: fiatAmountController,
|
||||
hintText: '0.00',
|
||||
borderColor:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
),
|
||||
),
|
||||
isAmountEditable: true,
|
||||
allAmountButton: false),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
|
@ -59,7 +59,8 @@ class SendTemplateCard extends StatelessWidget {
|
|||
hintText: sendTemplateViewModel.recipients.length > 1
|
||||
? S.of(context).template_name
|
||||
: S.of(context).send_name,
|
||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
borderColor:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
|
@ -69,107 +70,87 @@ class SendTemplateCard extends StatelessWidget {
|
|||
validator: sendTemplateViewModel.templateValidator),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return AddressTextField(
|
||||
selectedCurrency: template.selectedCurrency,
|
||||
controller: _addressController,
|
||||
onURIScanned: (uri) {
|
||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||
_addressController.text = paymentRequest.address;
|
||||
_cryptoAmountController.text = paymentRequest.amount;
|
||||
},
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
onPushPasteButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
),
|
||||
validator: sendTemplateViewModel.addressValidator,
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectCurrency();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
controller: _cryptoAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: template.selectedCurrency.title,
|
||||
isSelected: template.isCurrencySelected,
|
||||
onTap: sendTemplateViewModel.walletCurrencies.length > 1
|
||||
? () => _presentPicker(context)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
hintText: '0.0000',
|
||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: sendTemplateViewModel.amountValidator,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) {
|
||||
template.selectFiat();
|
||||
}
|
||||
},
|
||||
child: BaseTextFormField(
|
||||
focusNode: _fiatAmountFocus,
|
||||
controller: _fiatAmountController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||
prefixIcon: Observer(
|
||||
builder: (_) => PrefixCurrencyIcon(
|
||||
title: sendTemplateViewModel.fiatCurrency,
|
||||
isSelected: template.isFiatSelected)),
|
||||
hintText: '0.00',
|
||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
textStyle:
|
||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
child: Observer(builder: (context) {
|
||||
return AddressTextField(
|
||||
selectedCurrency: template.selectedCurrency,
|
||||
controller: _addressController,
|
||||
onURIScanned: (uri) {
|
||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||
_addressController.text = paymentRequest.address;
|
||||
_cryptoAmountController.text = paymentRequest.amount;
|
||||
},
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
onPushPasteButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
buttonColor:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderColor:
|
||||
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
),
|
||||
validator: sendTemplateViewModel.addressValidator,
|
||||
);
|
||||
}),
|
||||
),
|
||||
Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) template.setCryptoCurrency(true);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (context) => CurrencyAmountTextField(
|
||||
selectedCurrency: template.selectedCurrency.title,
|
||||
amountFocusNode: _cryptoAmountFocus,
|
||||
amountController: _cryptoAmountController,
|
||||
isSelected: template.isCryptoSelected,
|
||||
tag: template.selectedCurrency.tag,
|
||||
isPickerEnable: sendTemplateViewModel.hasMultipleTokens,
|
||||
onTapPicker: () => _presentPicker(context),
|
||||
currencyValueValidator: sendTemplateViewModel.amountValidator,
|
||||
isAmountEditable: true)),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor)
|
||||
],
|
||||
),
|
||||
),
|
||||
Focus(
|
||||
onFocusChange: (hasFocus) {
|
||||
if (hasFocus) template.setCryptoCurrency(false);
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (context) => CurrencyAmountTextField(
|
||||
selectedCurrency: sendTemplateViewModel.fiatCurrency,
|
||||
amountFocusNode: _fiatAmountFocus,
|
||||
amountController: _fiatAmountController,
|
||||
isSelected: !template.isCryptoSelected,
|
||||
hintText: '0.00',
|
||||
isAmountEditable: true)),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
|
@ -12,7 +13,11 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class OtherSettingsPage extends BasePage {
|
||||
OtherSettingsPage(this._otherSettingsViewModel);
|
||||
OtherSettingsPage(this._otherSettingsViewModel) {
|
||||
if (_otherSettingsViewModel.sendViewModel.isElectrumWallet) {
|
||||
bitcoin!.updateFeeRates(_otherSettingsViewModel.sendViewModel.wallet);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get title => S.current.other_settings;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue