Merge branch 'main' of https://github.com/cake-tech/cake_wallet into secure_storage_fresh_install

This commit is contained in:
fossephate 2024-10-28 17:01:44 -07:00
commit e56156c972
145 changed files with 2510 additions and 1352 deletions

View file

@ -224,6 +224,7 @@ jobs:
cd /opt/android/cake_wallet/build/app/outputs/flutter-apk
mkdir test-apk
cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk
cp app-x86_64-release.apk test-apk/${{env.BRANCH_NAME}}_x86.apk
- name: Upload Artifact
uses: kittaakos/upload-artifact-as-is@v0

View file

@ -1,35 +1,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="__APP_PACKAGE__">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!--bibo01 : hardware option-->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
<!-- bibo01 : hardware option-->
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- required for API 18 - 30 -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- required for API 18 - 30 -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- API 31+ -->
<!-- required for API <= 29 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<!-- API 31+ -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<application
android:name=".Application"
android:label="${APP_NAME}"

View file

@ -3,6 +3,7 @@
useSSL: true
-
uri: btc-electrum.cakewallet.com:50002
useSSL: true
isDefault: true
-
uri: electrs.cakewallet.com:50001

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,2 +1,3 @@
Enhance auto-address generation for Monero
Bug fixes and enhancements
Monero enhancements
Introducing StealthEx and LetxExchange
Bug fixes

View file

@ -1,4 +1,7 @@
Enable BIP39 by default for wallet creation also on Bitcoin/Litecoin (Electrum seed type is still accessible through advanced settings page)
Improve fee calculation for Bitcoin to protect against overpaying or underpaying
Enhance auto-address generation for Monero
Bug fixes and enhancements
Added Litecoin MWEB
Added wallet groups
Silent Payment enhancements for speed & reliability
Monero enhancements
Introducing StealthEx and LetxExchange
Additional ERC20 tokens scam detection
Bug fixes

View file

@ -15,7 +15,7 @@ These steps will help you configure and execute a build of CakeWallet from its s
### 1. Installing Package Dependencies
CakeWallet requires some packages to be install on your build system. You may easily install them on your build system with the following command:
CakeWallet requires some packages to be installed on your build system. You may easily install them on your build system with the following command:
`$ sudo apt install build-essential cmake pkg-config git curl autoconf libtool`
@ -145,7 +145,7 @@ Path to executable file will be:
# Flatpak
For package the built application into flatpak you need fistly to install `flatpak` and `flatpak-builder`:
For package the built application into flatpak you need firstly to install `flatpak` and `flatpak-builder`:
`$ sudo apt install flatpak flatpak-builder`

View file

@ -5,30 +5,31 @@ 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';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
class BitcoinHardwareWalletService {
BitcoinHardwareWalletService(this.ledger, this.device);
BitcoinHardwareWalletService(this.ledgerConnection);
final Ledger ledger;
final LedgerDevice device;
final LedgerConnection ledgerConnection;
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
final bitcoinLedgerApp = BitcoinLedgerApp(ledger);
Future<List<HardwareAccountData>> getAvailableAccounts(
{int index = 0, int limit = 5}) async {
final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device);
print(masterFp);
final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
for (final i in indexRange) {
final derivationPath = "m/84'/0'/$i'";
final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
final xpub =
await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
Bip32Slip10Secp256k1 hd =
Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
final address = generateP2WPKHAddress(
hd: hd, index: 0, network: BitcoinNetwork.mainnet);
accounts.add(HardwareAccountData(
address: address,

View file

@ -30,7 +30,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
static const allLitecoin = [
BitcoinReceivePageOption.p2wpkh,
BitcoinReceivePageOption.mweb
BitcoinReceivePageOption.mweb,
];
BitcoinAddressType toType() {

View file

@ -1,11 +1,13 @@
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_core/unspent_coin_type.dart';
class BitcoinTransactionCredentials {
BitcoinTransactionCredentials(this.outputs,
{required this.priority, this.feeRate});
{required this.priority, this.feeRate, this.coinTypeToSpendFrom = UnspentCoinType.any});
final List<OutputInfo> outputs;
final BitcoinTransactionPriority? priority;
final int? feeRate;
final UnspentCoinType coinTypeToSpendFrom;
}

View file

@ -5,13 +5,13 @@ 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/psbt_transaction_builder.dart';
import 'package:cw_core/encryption_file_utils.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_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';
@ -19,7 +19,7 @@ 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';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:mobx/mobx.dart';
part 'bitcoin_wallet.g.dart';
@ -61,8 +61,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance,
seedBytes: seedBytes,
encryptionFileUtils: encryptionFileUtils,
currency:
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
currency: networkParam == BitcoinNetwork.testnet
? CryptoCurrency.tbtc
: CryptoCurrency.btc,
alwaysScan: alwaysScan,
) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
@ -80,11 +81,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
mainHd: hd,
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
network: networkParam ?? network,
masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
masterHd:
seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
isHardwareWallet: walletInfo.isHardwareWallet,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
this.walletAddresses.isEnabledAutoGenerateSubaddress =
this.isEnabledAutoGenerateSubaddress;
});
}
@ -185,8 +189,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
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;
@ -228,15 +234,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
);
}
Ledger? _ledger;
LedgerDevice? _ledgerDevice;
LedgerConnection? _ledgerConnection;
BitcoinLedgerApp? _bitcoinLedgerApp;
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
_ledger = setLedger;
_ledgerDevice = setLedgerDevice;
_bitcoinLedgerApp =
BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
@override
void setLedgerConnection(LedgerConnection connection) {
_ledgerConnection = connection;
_bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!,
derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
@ -251,12 +256,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async {
final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!);
final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
for (final utxo in utxos) {
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
final rawTx =
await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
final publicKeyAndDerivationPath =
publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
utxo: utxo.utxo,
@ -268,10 +275,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
));
}
final psbt =
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final psbt = PSBTTransactionBuild(
inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt);
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
}
@ -279,14 +286,16 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future<String> signMessage(String message, {String? address = null}) async {
if (walletInfo.isHardwareWallet) {
final addressEntry = address != null
? walletAddresses.allAddresses.firstWhere((element) => element.address == address)
? walletAddresses.allAddresses
.firstWhere((element) => element.address == address)
: null;
final index = addressEntry?.index ?? 0;
final isChange = addressEntry?.isHidden == true ? 1 : 0;
final accountPath = walletInfo.derivationInfo?.derivationPath;
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
final derivationPath =
accountPath != null ? "$accountPath/$isChange/$index" : null;
final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!,
final signature = await _bitcoinLedgerApp!.signMessage(
message: ascii.encode(message), signDerivationPath: derivationPath);
return base64Encode(signature);
}

View file

@ -15,6 +15,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required super.mainHd,
required super.sideHd,
required super.network,
required super.isHardwareWallet,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,

View file

@ -68,8 +68,8 @@ class ElectrumClient {
try {
await socket?.close();
socket = null;
} catch (_) {}
socket = null;
try {
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
@ -102,7 +102,8 @@ class ElectrumClient {
return;
}
_setConnectionStatus(ConnectionStatus.connected);
// use ping to determine actual connection status since we could've just not timed out yet:
// _setConnectionStatus(ConnectionStatus.connected);
socket!.listen(
(Uint8List event) {
@ -116,7 +117,7 @@ class ElectrumClient {
_parseResponse(message);
}
} catch (e) {
print(e.toString());
print("socket.listen: $e");
}
},
onError: (Object error) {
@ -125,14 +126,15 @@ class ElectrumClient {
unterminatedString = '';
},
onDone: () {
print("SOCKET CLOSED!!!!!");
unterminatedString = '';
try {
if (host == socket?.address.host) {
socket?.destroy();
if (host == socket?.address.host || socket == null) {
_setConnectionStatus(ConnectionStatus.disconnected);
socket?.destroy();
}
} catch (e) {
print(e.toString());
print("onDone: $e");
}
},
cancelOnError: true,
@ -177,7 +179,7 @@ class ElectrumClient {
unterminatedString = '';
}
} catch (e) {
print(e.toString());
print("parse $e");
}
}
@ -190,7 +192,7 @@ class ElectrumClient {
try {
await callWithTimeout(method: 'server.ping');
_setConnectionStatus(ConnectionStatus.connected);
} on RequestFailedTimeoutException catch (_) {
} catch (_) {
_setConnectionStatus(ConnectionStatus.disconnected);
}
}
@ -430,7 +432,7 @@ class ElectrumClient {
return subscription;
} catch (e) {
print(e.toString());
print("subscribe $e");
return null;
}
}
@ -469,7 +471,8 @@ class ElectrumClient {
return completer.future;
} catch (e) {
print(e.toString());
print("callWithTimeout $e");
rethrow;
}
}
@ -536,6 +539,12 @@ class ElectrumClient {
onConnectionStatusChange?.call(status);
_connectionStatus = status;
_isConnected = status == ConnectionStatus.connected;
if (!_isConnected) {
try {
socket?.destroy();
} catch (_) {}
socket = null;
}
}
void _handleResponse(Map<String, dynamic> response) {

View file

@ -4,9 +4,8 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_from_output.dart';
@ -23,10 +22,11 @@ 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_wallet.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
@ -38,9 +38,10 @@ 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:cw_core/unspent_coin_type.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart';
@ -51,9 +52,10 @@ part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
abstract class ElectrumWalletBase
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
with Store, WalletKeysFile {
abstract class ElectrumWalletBase extends WalletBase<
ElectrumBalance,
ElectrumTransactionHistory,
ElectrumTransactionInfo> with Store, WalletKeysFile {
ElectrumWalletBase({
required String password,
required WalletInfo walletInfo,
@ -69,8 +71,8 @@ abstract class ElectrumWalletBase
ElectrumBalance? initialBalance,
CryptoCurrency? currency,
this.alwaysScan,
}) : accountHD =
getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
}) : accountHD = getAccountHDWallet(
currency, network, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_feeRates = <int>[],
@ -105,8 +107,12 @@ abstract class ElectrumWalletBase
sharedPrefs.complete(SharedPreferences.getInstance());
}
static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
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");
@ -117,8 +123,9 @@ abstract class ElectrumWalletBase
case CryptoCurrency.btc:
case CryptoCurrency.ltc:
case CryptoCurrency.tbtc:
return Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network))
.derivePath(_hardenedDerivationPath(
derivationInfo?.derivationPath ?? electrum_path))
as Bip32Slip10Secp256k1;
case CryptoCurrency.bch:
return bitcoinCashHDWallet(seedBytes);
@ -127,15 +134,26 @@ abstract class ElectrumWalletBase
}
}
return Bip32Slip10Secp256k1.fromExtendedKey(xpub!);
return Bip32Slip10Secp256k1.fromExtendedKey(
xpub!, getKeyNetVersion(network));
}
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'")
as Bip32Slip10Secp256k1;
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10;
static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) {
switch (network) {
case LitecoinNetwork.mainnet:
return Bip44Conf.litecoinMainNet.altKeyNetVer;
default:
return null;
}
}
bool? alwaysScan;
final Bip32Slip10Secp256k1 accountHD;
@ -168,7 +186,10 @@ abstract class ElectrumWalletBase
@observable
SyncStatus syncStatus;
Set<String> get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet();
Set<String> get addressesSet => walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb)
.map((addr) => addr.address)
.toSet();
List<String> get scriptHashes => walletAddresses.addressesByReceiveType
.where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress)
@ -247,7 +268,7 @@ abstract class ElectrumWalletBase
int? _currentChainTip;
Future<int> getCurrentChainTip() async {
if (_currentChainTip != null) {
if ((_currentChainTip ?? 0) > 0) {
return _currentChainTip!;
}
_currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
@ -299,6 +320,7 @@ abstract class ElectrumWalletBase
@action
Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
if (this is! BitcoinWallet) return;
final chainTip = chainTipParam ?? await getUpdatedChainTip();
if (chainTip == height) {
@ -465,7 +487,7 @@ abstract class ElectrumWalletBase
}
} catch (e, stacktrace) {
print(stacktrace);
print(e.toString());
print("startSync $e");
syncStatus = FailedSyncStatus();
}
}
@ -477,10 +499,10 @@ abstract class ElectrumWalletBase
final response =
await http.get(Uri.parse("http://mempool.cakewallet.com:8999/api/v1/fees/recommended"));
final result = json.decode(response.body) as Map<String, num>;
final slowFee = result['economyFee']?.toInt() ?? 0;
int mediumFee = result['hourFee']?.toInt() ?? 0;
int fastFee = result['fastestFee']?.toInt() ?? 0;
final result = json.decode(response.body) as Map<String, dynamic>;
final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
int fastFee = (result['fastestFee'] as num?)?.toInt() ?? 0;
if (slowFee == mediumFee) {
mediumFee++;
}
@ -489,7 +511,9 @@ abstract class ElectrumWalletBase
}
_feeRates = [slowFee, mediumFee, fastFee];
return;
} catch (_) {}
} catch (e) {
print(e);
}
}
final feeRates = await electrumClient.feeRates(network: network);
@ -569,7 +593,7 @@ abstract class ElectrumWalletBase
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
} catch (e, stacktrace) {
print(stacktrace);
print(e.toString());
print("connectToNode $e");
syncStatus = FailedSyncStatus();
}
}
@ -583,6 +607,7 @@ abstract class ElectrumWalletBase
required int credentialsAmount,
required bool paysToSilentPayment,
int? inputsCount,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) {
List<UtxoWithAddress> utxos = [];
List<Outpoint> vinOutpoints = [];
@ -593,7 +618,20 @@ abstract class ElectrumWalletBase
bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount;
final availableInputs = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList();
final availableInputs = unspentCoins.where((utx) {
if (!utx.isSending || utx.isFrozen) {
return false;
}
switch (coinTypeToSpendFrom) {
case UnspentCoinType.mweb:
return utx.bitcoinAddressRecord.type == SegwitAddresType.mweb;
case UnspentCoinType.nonMweb:
return utx.bitcoinAddressRecord.type != SegwitAddresType.mweb;
case UnspentCoinType.any:
return true;
}
}).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
for (int i = 0; i < availableInputs.length; i++) {
@ -614,8 +652,9 @@ abstract class ElectrumWalletBase
ECPrivate? privkey;
bool? isSilentPayment = false;
final hd =
utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
final hd = utx.bitcoinAddressRecord.isHidden
? walletAddresses.sideHd
: walletAddresses.mainHd;
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
@ -700,11 +739,13 @@ abstract class ElectrumWalletBase
String? memo,
int credentialsAmount = 0,
bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async {
final utxoDetails = _createUTXOS(
sendAll: true,
credentialsAmount: credentialsAmount,
paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
int fee = await calcFee(
@ -766,17 +807,20 @@ abstract class ElectrumWalletBase
Future<EstimatedTxResult> estimateTxForAmount(
int credentialsAmount,
List<BitcoinOutput> outputs,
List<BitcoinOutput> updatedOutputs,
int feeRate, {
int? inputsCount,
String? memo,
bool? useUnconfirmed,
bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async {
final utxoDetails = _createUTXOS(
sendAll: false,
credentialsAmount: credentialsAmount,
inputsCount: inputsCount,
paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length;
@ -792,10 +836,12 @@ abstract class ElectrumWalletBase
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
@ -803,19 +849,38 @@ abstract class ElectrumWalletBase
}
final changeAddress = await walletAddresses.getChangeAddress(
outputs: outputs,
utxoDetails: utxoDetails,
inputs: utxoDetails.availableInputs,
outputs: updatedOutputs,
);
final address = RegexUtils.addressTypeFromStr(changeAddress, network);
final address = RegexUtils.addressTypeFromStr(changeAddress.address, network);
updatedOutputs.add(BitcoinOutput(
address: address,
value: BigInt.from(amountLeftForChangeAndFee),
isChange: true,
));
outputs.add(BitcoinOutput(
address: address,
value: BigInt.from(amountLeftForChangeAndFee),
isChange: true,
));
// Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets
final changeDerivationPath =
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
"/${changeAddress.isHidden ? "1" : "0"}"
"/${changeAddress.index}";
utxoDetails.publicKeys[address.pubKeyHash()] =
PublicKeyWithDerivationPath('', changeDerivationPath);
// calcFee updates the silent payment outputs to calculate the tx size accounting
// for taproot addresses, but if more inputs are needed to make up for fees,
// the silent payment outputs need to be recalculated for the new inputs
var temp = outputs.map((output) => output).toList();
int fee = await calcFee(
utxos: utxoDetails.utxos,
outputs: outputs,
// Always take only not updated bitcoin outputs here so for every estimation
// the SP outputs are re-generated to the proper taproot addresses
outputs: temp,
network: network,
memo: memo,
feeRate: feeRate,
@ -823,18 +888,25 @@ abstract class ElectrumWalletBase
vinOutpoints: utxoDetails.vinOutpoints,
);
updatedOutputs.clear();
updatedOutputs.addAll(temp);
if (fee == 0) {
throw BitcoinTransactionNoFeeException();
}
int amount = credentialsAmount;
final lastOutput = outputs.last;
final lastOutput = updatedOutputs.last;
final amountLeftForChange = amountLeftForChangeAndFee - fee;
print(amountLeftForChangeAndFee);
if (!_isBelowDust(amountLeftForChange)) {
// Here, lastOutput already is change, return the amount left without the fee to the user's address.
updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput(
address: lastOutput.address,
value: BigInt.from(amountLeftForChange),
isSilentPayment: lastOutput.isSilentPayment,
isChange: true,
);
outputs[outputs.length - 1] = BitcoinOutput(
address: lastOutput.address,
value: BigInt.from(amountLeftForChange),
@ -843,6 +915,7 @@ abstract class ElectrumWalletBase
);
} else {
// If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
updatedOutputs.removeLast();
outputs.removeLast();
// Still has inputs to spend before failing
@ -850,17 +923,21 @@ abstract class ElectrumWalletBase
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
hasSilentPayment: hasSilentPayment,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
final estimatedSendAll = await estimateSendAllTx(
outputs,
updatedOutputs,
feeRate,
memo: memo,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
if (estimatedSendAll.amount == credentialsAmount) {
@ -890,15 +967,18 @@ abstract class ElectrumWalletBase
if (spendingAllCoins) {
throw BitcoinTransactionWrongBalanceException();
} else {
updatedOutputs.removeLast();
outputs.removeLast();
return estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRate,
inputsCount: utxoDetails.utxos.length + 1,
memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
}
@ -956,6 +1036,7 @@ abstract class ElectrumWalletBase
final hasMultiDestination = transactionCredentials.outputs.length > 1;
final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll;
final memo = transactionCredentials.outputs.first.memo;
final coinTypeToSpendFrom = transactionCredentials.coinTypeToSpendFrom;
int credentialsAmount = 0;
bool hasSilentPayment = false;
@ -1004,28 +1085,34 @@ abstract class ElectrumWalletBase
: feeRate(transactionCredentials.priority!);
EstimatedTxResult estimatedTx;
final updatedOutputs =
outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList();
if (sendAll) {
estimatedTx = await estimateSendAllTx(
outputs,
updatedOutputs,
feeRateInt,
memo: memo,
credentialsAmount: credentialsAmount,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
} else {
estimatedTx = await estimateTxForAmount(
credentialsAmount,
outputs,
updatedOutputs,
feeRateInt,
memo: memo,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
if (walletInfo.isHardwareWallet) {
final transaction = await buildHardwareWalletTransaction(
utxos: estimatedTx.utxos,
outputs: outputs,
outputs: updatedOutputs,
publicKeys: estimatedTx.publicKeys,
fee: BigInt.from(estimatedTx.fee),
network: network,
@ -1055,7 +1142,7 @@ abstract class ElectrumWalletBase
if (network is BitcoinCashNetwork) {
txb = ForkedTransactionBuilder(
utxos: estimatedTx.utxos,
outputs: outputs,
outputs: updatedOutputs,
fee: BigInt.from(estimatedTx.fee),
network: network,
memo: estimatedTx.memo,
@ -1065,7 +1152,7 @@ abstract class ElectrumWalletBase
} else {
txb = BitcoinTransactionBuilder(
utxos: estimatedTx.utxos,
outputs: outputs,
outputs: updatedOutputs,
fee: BigInt.from(estimatedTx.fee),
network: network,
memo: estimatedTx.memo,
@ -1125,6 +1212,7 @@ abstract class ElectrumWalletBase
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs,
utxos: estimatedTx.utxos,
)..addListener((transaction) async {
transactionHistory.addOne(transaction);
if (estimatedTx.spendsSilentPayment) {
@ -1145,6 +1233,9 @@ abstract class ElectrumWalletBase
}
}
void setLedgerConnection(ledger.LedgerConnection connection) =>
throw UnimplementedError();
Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs,
required BigInt fee,
@ -1174,6 +1265,7 @@ abstract class ElectrumWalletBase
'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(),
'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(),
'mweb_addresses': walletAddresses.mwebAddresses.map((addr) => addr.toJSON()).toList(),
'alwaysScan': alwaysScan,
});
int feeRate(TransactionPriority priority) {
@ -1291,7 +1383,7 @@ abstract class ElectrumWalletBase
}
@override
Future<void> close() async {
Future<void> close({required bool shouldCleanup}) async {
try {
await _receiveStream?.cancel();
await electrumClient.close();
@ -1313,12 +1405,16 @@ abstract class ElectrumWalletBase
});
}
// Set the balance of all non-silent payment addresses to 0 before updating
walletAddresses.allAddresses.forEach((addr) {
if(addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
// Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb)
.forEach((addr) {
if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
});
await Future.wait(walletAddresses.allAddresses.map((address) async {
await Future.wait(walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb)
.map((address) async {
updatedUnspentCoins.addAll(await fetchUnspent(address));
}));
@ -1426,7 +1522,7 @@ abstract class ElectrumWalletBase
await unspentCoinsInfo.deleteAll(keys);
}
} catch (e) {
print(e.toString());
print("refreshUnspentCoinsInfo $e");
}
}
@ -1497,7 +1593,9 @@ abstract class ElectrumWalletBase
final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network);
final privkey = generateECPrivate(
hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
hd: addressRecord.isHidden
? walletAddresses.sideHd
: walletAddresses.mainHd,
index: addressRecord.index,
network: network);
@ -1679,7 +1777,8 @@ abstract class ElectrumWalletBase
if (height != null) {
if (time == null && height > 0) {
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000)
.round();
}
if (confirmations == null) {
@ -1770,7 +1869,7 @@ abstract class ElectrumWalletBase
return historiesWithDetails;
} catch (e) {
print(e.toString());
print("fetchTransactions $e");
return {};
}
}
@ -1825,6 +1924,8 @@ abstract class ElectrumWalletBase
Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory(
BitcoinAddressRecord addressRecord, int? currentHeight) async {
String txid = "";
try {
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
@ -1834,7 +1935,7 @@ abstract class ElectrumWalletBase
addressRecord.setAsUsed();
await Future.wait(history.map((transaction) async {
final txid = transaction['tx_hash'] as String;
txid = transaction['tx_hash'] as String;
final height = transaction['height'] as int;
final storedTx = transactionHistory.transactions[txid];
@ -1842,7 +1943,9 @@ abstract class ElectrumWalletBase
if (height > 0) {
storedTx.height = height;
// the tx's block itself is the first confirmation so add 1
if (currentHeight != null) storedTx.confirmations = currentHeight - height + 1;
if ((currentHeight ?? 0) > 0) {
storedTx.confirmations = currentHeight! - height + 1;
}
storedTx.isPending = storedTx.confirmations == 0;
}
@ -1865,22 +1968,31 @@ abstract class ElectrumWalletBase
}
return historiesWithDetails;
} catch (e) {
print(e.toString());
} catch (e, stacktrace) {
_onError?.call(FlutterErrorDetails(
exception: "$txid - $e",
stack: stacktrace,
library: this.runtimeType.toString(),
));
return {};
}
}
Future<void> updateTransactions() async {
print("updateTransactions() called!");
try {
if (_isTransactionUpdating) {
return;
}
await getCurrentChainTip();
transactionHistory.transactions.values.forEach((tx) async {
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) {
tx.confirmations = await getCurrentChainTip() - tx.height! + 1;
transactionHistory.transactions.values.forEach((tx) {
if (tx.unspents != null &&
tx.unspents!.isNotEmpty &&
tx.height != null &&
tx.height! > 0 &&
(_currentChainTip ?? 0) > 0) {
tx.confirmations = _currentChainTip! - tx.height! + 1;
}
});
@ -1897,13 +2009,25 @@ abstract class ElectrumWalletBase
Future<void> subscribeForUpdates() async {
final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
(address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)),
(address) =>
!_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)) &&
address.type != SegwitAddresType.mweb,
);
await Future.wait(unsubscribedScriptHashes.map((address) async {
final sh = address.getScriptHash(network);
await _scripthashesUpdateSubject[sh]?.close();
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
if (!(_scripthashesUpdateSubject[sh]?.isClosed ?? true)) {
try {
await _scripthashesUpdateSubject[sh]?.close();
} catch (e) {
print("failed to close: $e");
}
}
try {
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
} catch (e) {
print("failed scripthashUpdate: $e");
}
_scripthashesUpdateSubject[sh]?.listen((event) async {
try {
await updateUnspentsForAddress(address);
@ -1912,7 +2036,7 @@ abstract class ElectrumWalletBase
await _fetchAddressHistory(address, await getCurrentChainTip());
} catch (e, s) {
print(e.toString());
print("sub error: $e");
_onError?.call(FlutterErrorDetails(
exception: e,
stack: s,
@ -1990,6 +2114,7 @@ abstract class ElectrumWalletBase
}
Future<void> updateBalance() async {
print("updateBalance() called!");
balance[currency] = await fetchBalances();
await save();
}
@ -2098,6 +2223,7 @@ abstract class ElectrumWalletBase
@action
void _onConnectionStatusChange(ConnectionStatus status) {
switch (status) {
case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus ||
@ -2109,19 +2235,26 @@ abstract class ElectrumWalletBase
break;
case ConnectionStatus.disconnected:
syncStatus = NotConnectedSyncStatus();
if (syncStatus is! NotConnectedSyncStatus) {
syncStatus = NotConnectedSyncStatus();
}
break;
case ConnectionStatus.failed:
syncStatus = LostConnectionSyncStatus();
if (syncStatus is! LostConnectionSyncStatus) {
syncStatus = LostConnectionSyncStatus();
}
break;
case ConnectionStatus.connecting:
syncStatus = ConnectingSyncStatus();
if (syncStatus is! ConnectingSyncStatus) {
syncStatus = ConnectingSyncStatus();
}
break;
default:
}
}
void _syncStatusReaction(SyncStatus syncStatus) async {
print("SYNC_STATUS_CHANGE: ${syncStatus}");
if (syncStatus is SyncingSyncStatus) {
return;
}

View file

@ -1,7 +1,9 @@
import 'dart:io' show Platform;
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/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@ -34,6 +36,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required this.mainHd,
required this.sideHd,
required this.network,
required this.isHardwareWallet,
List<BitcoinAddressRecord>? initialAddresses,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
@ -42,6 +45,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
List<BitcoinAddressRecord>? initialMwebAddresses,
Bip32Slip10Secp256k1? masterHd,
BitcoinAddressType? initialAddressPageType,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
addressesByReceiveType =
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
@ -110,6 +114,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final BasedUtxoNetwork network;
final Bip32Slip10Secp256k1 mainHd;
final Bip32Slip10Secp256k1 sideHd;
final bool isHardwareWallet;
@observable
SilentPaymentOwner? silentAddress;
@ -238,13 +243,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
} else if (walletInfo.type == WalletType.litecoin) {
await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
await _generateInitialAddresses(type: SegwitAddresType.mweb);
if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
await _generateInitialAddresses(type: SegwitAddresType.mweb);
}
} else if (walletInfo.type == WalletType.bitcoin) {
await _generateInitialAddresses();
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
if (!isHardwareWallet) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
}
}
updateAddressesByMatch();
@ -263,7 +272,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
@action
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async {
Future<BitcoinAddressRecord> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
updateChangeAddresses();
if (changeAddresses.isEmpty) {
@ -278,7 +287,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
updateChangeAddresses();
final address = changeAddresses[currentChangeAddressIndex].address;
final address = changeAddresses[currentChangeAddressIndex];
currentChangeAddressIndex += 1;
return address;
}
@ -322,7 +331,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
);
silentAddresses.add(address);
updateAddressesByMatch();
Future.delayed(Duration.zero, () => updateAddressesByMatch());
return address;
}
@ -339,7 +348,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
network: network,
);
_addresses.add(address);
updateAddressesByMatch();
Future.delayed(Duration.zero, () => updateAddressesByMatch());
return address;
}
@ -474,7 +483,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
await saveAddressesInBox();
} catch (e) {
print(e.toString());
print("updateAddresses $e");
}
}
@ -666,7 +675,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
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;

View file

@ -24,6 +24,7 @@ class ElectrumWalletSnapshot {
required this.silentAddresses,
required this.silentAddressIndex,
required this.mwebAddresses,
required this.alwaysScan,
this.passphrase,
this.derivationType,
this.derivationPath,
@ -46,6 +47,7 @@ class ElectrumWalletSnapshot {
List<BitcoinAddressRecord> addresses;
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
List<BitcoinAddressRecord> mwebAddresses;
bool alwaysScan;
ElectrumBalance balance;
Map<String, int> regularAddressIndex;
@ -54,15 +56,15 @@ class ElectrumWalletSnapshot {
DerivationType? derivationType;
String? derivationPath;
static Future<ElectrumWalletSnapshot> load(
EncryptionFileUtils encryptionFileUtils, String name, WalletType type, String password, BasedUtxoNetwork network) async {
static Future<ElectrumWalletSnapshot> load(EncryptionFileUtils encryptionFileUtils, String name,
WalletType type, String password, BasedUtxoNetwork network) async {
final path = await pathForWallet(name: name, type: type);
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String?;
final xpub = data['xpub'] as String?;
final passphrase = data['passphrase'] as String? ?? '';
final addressesTmp = data['addresses'] as List? ?? <Object>[];
final addresses = addressesTmp
.whereType<String>()
@ -81,6 +83,8 @@ class ElectrumWalletSnapshot {
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
.toList();
final alwaysScan = data['alwaysScan'] as bool? ?? false;
final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
@ -124,6 +128,7 @@ class ElectrumWalletSnapshot {
silentAddresses: silentAddresses,
silentAddressIndex: silentAddressIndex,
mwebAddresses: mwebAddresses,
alwaysScan: alwaysScan,
);
}
}

View file

@ -0,0 +1,46 @@
import 'dart:async';
import 'package:bitcoin_base/bitcoin_base.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_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_litecoin/ledger_litecoin.dart';
class LitecoinHardwareWalletService {
LitecoinHardwareWalletService(this.ledgerConnection);
final LedgerConnection ledgerConnection;
Future<List<HardwareAccountData>> getAvailableAccounts(
{int index = 0, int limit = 5}) async {
final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
await litecoinLedgerApp.getVersion();
final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
for (final i in indexRange) {
final derivationPath = "m/84'/2'/$i'";
final xpub = await litecoinLedgerApp.getXPubKey(
accountsDerivationPath: derivationPath,
xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion)
.childKey(Bip32KeyIndex(0));
final address = generateP2WPKHAddress(
hd: hd, index: 0, network: LitecoinNetwork.mainnet);
accounts.add(HardwareAccountData(
address: address,
accountIndex: i,
derivationPath: derivationPath,
xpub: xpub,
));
}
return accounts;
}
}

View file

@ -1,5 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart' as convert;
import 'dart:math';
import 'package:collection/collection.dart';
@ -37,6 +39,8 @@ import 'package:cw_core/wallet_keys_file.dart';
import 'package:flutter/foundation.dart';
import 'package:grpc/grpc.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_litecoin/ledger_litecoin.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_mweb/cw_mweb.dart';
@ -50,12 +54,13 @@ class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
abstract class LitecoinWalletBase extends ElectrumWallet with Store {
LitecoinWalletBase({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
required EncryptionFileUtils encryptionFileUtils,
Uint8List? seedBytes,
String? mnemonic,
String? xpub,
String? passphrase,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
@ -68,6 +73,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}) : super(
mnemonic: mnemonic,
password: password,
xpub: xpub,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
network: LitecoinNetwork.mainnet,
@ -76,9 +82,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
seedBytes: seedBytes,
encryptionFileUtils: encryptionFileUtils,
currency: CryptoCurrency.ltc,
alwaysScan: alwaysScan,
) {
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
mwebEnabled = alwaysScan ?? false;
if (seedBytes != null) {
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
"m/1000'") as Bip32Slip10Secp256k1;
mwebEnabled = alwaysScan ?? false;
} else {
mwebHd = null;
mwebEnabled = false;
}
walletAddresses = LitecoinWalletAddresses(
walletInfo,
initialAddresses: initialAddresses,
@ -90,23 +103,56 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
network: network,
mwebHd: mwebHd,
mwebEnabled: mwebEnabled,
isHardwareWallet: walletInfo.isHardwareWallet,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
reaction((_) => mwebSyncStatus, (status) async {
if (mwebSyncStatus is FailedSyncStatus) {
// we failed to connect to mweb, check if we are connected to the litecoin node:
late int nodeHeight;
try {
nodeHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
} catch (_) {
nodeHeight = 0;
}
if (nodeHeight == 0) {
// we aren't connected to the litecoin node, so the current electrum_wallet reactions will take care of this case for us
} else {
// we're connected to the litecoin node, but we failed to connect to mweb, try again after a few seconds:
await CwMweb.stop();
await Future.delayed(const Duration(seconds: 5));
startSync();
}
} else if (mwebSyncStatus is SyncingSyncStatus) {
syncStatus = mwebSyncStatus;
} else if (mwebSyncStatus is SyncronizingSyncStatus) {
if (syncStatus is! SyncronizingSyncStatus) {
syncStatus = mwebSyncStatus;
}
} else if (mwebSyncStatus is SyncedSyncStatus) {
if (syncStatus is! SyncedSyncStatus) {
syncStatus = mwebSyncStatus;
}
}
});
}
late final Bip32Slip10Secp256k1 mwebHd;
late final Bip32Slip10Secp256k1? mwebHd;
late final Box<MwebUtxo> mwebUtxosBox;
Timer? _syncTimer;
Timer? _feeRatesTimer;
Timer? _processingTimer;
StreamSubscription<Utxo>? _utxoStream;
late RpcClient _stub;
late bool mwebEnabled;
bool processingUtxos = false;
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
@observable
SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
static Future<LitecoinWallet> create(
{required String mnemonic,
@ -216,31 +262,28 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
return LitecoinWallet(
mnemonic: keysData.mnemonic!,
mnemonic: keysData.mnemonic,
xpub: keysData.xPub,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp?.addresses,
initialMwebAddresses: snp?.mwebAddresses,
initialBalance: snp?.balance,
seedBytes: seedBytes!,
seedBytes: seedBytes,
passphrase: passphrase,
encryptionFileUtils: encryptionFileUtils,
initialRegularAddressIndex: snp?.regularAddressIndex,
initialChangeAddressIndex: snp?.changeAddressIndex,
addressPageType: snp?.addressPageType,
alwaysScan: alwaysScan,
alwaysScan: snp?.alwaysScan,
);
}
Future<void> waitForMwebAddresses() async {
print("waitForMwebAddresses() called!");
// ensure that we have the full 1000 mweb addresses generated before continuing:
// should no longer be needed, but leaving here just in case
// final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
// while (mwebAddrs.length < 1000) {
// print("waiting for mweb addresses to finish generating...");
// await Future.delayed(const Duration(milliseconds: 1000));
// }
await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020);
}
@ -248,58 +291,60 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override
Future<void> startSync() async {
print("startSync() called!");
if (syncStatus is SyncronizingSyncStatus) {
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
if (!mwebEnabled) {
try {
// in case we're switching from a litecoin wallet that had mweb enabled
CwMweb.stop();
} catch (_) {}
super.startSync();
return;
}
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
try {
syncStatus = SyncronizingSyncStatus();
await subscribeForUpdates();
updateFeeRates();
if (mwebSyncStatus is SyncronizingSyncStatus) {
return;
}
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
_syncTimer?.cancel();
try {
mwebSyncStatus = SyncronizingSyncStatus();
try {
await subscribeForUpdates();
} catch (e) {
print("failed to subcribe for updates: $e");
}
updateFeeRates();
_feeRatesTimer?.cancel();
_feeRatesTimer =
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
if (!mwebEnabled) {
try {
await updateAllUnspents();
await updateTransactions();
await updateBalance();
syncStatus = SyncedSyncStatus();
} catch (e, s) {
print(e);
print(s);
syncStatus = FailedSyncStatus();
}
return;
}
print("START SYNC FUNCS");
await waitForMwebAddresses();
await getStub();
await processMwebUtxos();
await updateTransactions();
await updateUnspent();
await updateBalance();
} catch (e) {
print("failed to start mweb sync: $e");
syncStatus = FailedSyncStatus();
print("DONE SYNC FUNCS");
} catch (e, s) {
print("mweb sync failed: $e $s");
mwebSyncStatus = FailedSyncStatus(error: "mweb sync failed: $e");
return;
}
_syncTimer?.cancel();
_syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) async {
if (syncStatus is FailedSyncStatus) return;
print("SYNCING....");
_syncTimer = Timer.periodic(const Duration(milliseconds: 3000), (timer) async {
if (mwebSyncStatus is FailedSyncStatus) {
_syncTimer?.cancel();
return;
}
final nodeHeight =
await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node
if (nodeHeight == 0) {
// we aren't connected to the ltc node yet
if (syncStatus is! NotConnectedSyncStatus) {
syncStatus = FailedSyncStatus(error: "Failed to connect to Litecoin node");
if (mwebSyncStatus is! NotConnectedSyncStatus) {
mwebSyncStatus = FailedSyncStatus(error: "litecoin node isn't connected");
}
return;
}
@ -309,12 +354,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
try {
if (resp.blockHeaderHeight < nodeHeight) {
int h = resp.blockHeaderHeight;
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
} else if (resp.mwebHeaderHeight < nodeHeight) {
int h = resp.mwebHeaderHeight;
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
} else if (resp.mwebUtxosHeight < nodeHeight) {
syncStatus = SyncingSyncStatus(1, 0.999);
mwebSyncStatus = SyncingSyncStatus(1, 0.999);
} else {
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
@ -325,6 +370,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
int txHeight = transaction.height ?? resp.mwebUtxosHeight;
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
if (transaction.confirmations == confirmations) continue;
if (transaction.confirmations == 0) {
updateBalance();
}
transaction.confirmations = confirmations;
transactionHistory.addOne(transaction);
}
@ -332,17 +380,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
// prevent unnecessary reaction triggers:
if (syncStatus is! SyncedSyncStatus) {
if (mwebSyncStatus is! SyncedSyncStatus) {
// mwebd is synced, but we could still be processing incoming utxos:
if (!processingUtxos) {
syncStatus = SyncedSyncStatus();
mwebSyncStatus = SyncedSyncStatus();
}
}
return;
}
} catch (e) {
print("error syncing: $e");
syncStatus = FailedSyncStatus(error: e.toString());
mwebSyncStatus = FailedSyncStatus(error: e.toString());
}
});
}
@ -350,10 +398,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@action
@override
Future<void> stopSync() async {
print("stopSync() called!");
_syncTimer?.cancel();
_utxoStream?.cancel();
_feeRatesTimer?.cancel();
await CwMweb.stop();
print("stopped syncing!");
}
Future<void> initMwebUtxosBox() async {
@ -388,7 +438,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool? usingElectrs,
}) async {
_syncTimer?.cancel();
int oldHeight = walletInfo.restoreHeight;
await walletInfo.updateRestoreHeight(height);
// go through mwebUtxos and clear any that are above the new restore height:
@ -425,9 +474,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
await initMwebUtxosBox();
}
Future<void> handleIncoming(MwebUtxo utxo, RpcClient stub) async {
Future<void> handleIncoming(MwebUtxo utxo) async {
print("handleIncoming() called!");
final status = await stub.status(StatusRequest());
final status = await CwMweb.status(StatusRequest());
var date = DateTime.now();
var confirmations = 0;
if (utxo.height > 0) {
@ -493,6 +542,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
Future<void> processMwebUtxos() async {
print("processMwebUtxos() called!");
if (!mwebEnabled) {
return;
}
@ -502,15 +552,15 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final req = UtxosRequest(scanSecret: scanSecret, fromHeight: restoreHeight);
// process new utxos as they come in:
_utxoStream?.cancel();
await _utxoStream?.cancel();
ResponseStream<Utxo>? responseStream = await CwMweb.utxos(req);
if (responseStream == null) {
throw Exception("failed to get utxos stream!");
}
_utxoStream = responseStream.listen((Utxo sUtxo) async {
// we're processing utxos, so our balance could still be innacurate:
if (syncStatus is! SyncronizingSyncStatus && syncStatus is! SyncingSyncStatus) {
syncStatus = SyncronizingSyncStatus();
if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
mwebSyncStatus = SyncronizingSyncStatus();
processingUtxos = true;
_processingTimer?.cancel();
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
@ -527,10 +577,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
value: sUtxo.value.toInt(),
);
// if (mwebUtxosBox.containsKey(utxo.outputId)) {
// // we've already stored this utxo, skip it:
// return;
// }
if (mwebUtxosBox.containsKey(utxo.outputId)) {
// we've already stored this utxo, skip it:
// but do update the utxo height if it's somehow different:
final existingUtxo = mwebUtxosBox.get(utxo.outputId);
if (existingUtxo!.height != utxo.height) {
print(
"updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
existingUtxo.height = utxo.height;
await mwebUtxosBox.put(utxo.outputId, existingUtxo);
}
return;
}
await updateUnspent();
await updateBalance();
@ -544,7 +602,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
await mwebUtxosBox.put(utxo.outputId, utxo);
await handleIncoming(utxo, _stub);
await handleIncoming(utxo);
});
}
@ -576,7 +634,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final height = await electrumClient.getCurrentBlockChainTip();
if (height == null || status.blockHeaderHeight != height) return;
if (status.mwebUtxosHeight != height) return; // we aren't synced
int amount = 0;
Set<String> inputAddresses = {};
var output = convert.AccumulatorSink<Digest>();
@ -662,6 +719,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
Future<void> updateUnspent() async {
print("updateUnspent() called!");
await checkMwebUtxosSpent();
await updateAllUnspents();
}
@ -669,13 +727,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override
@action
Future<void> updateAllUnspents() async {
// get ltc unspents:
await super.updateAllUnspents();
if (!mwebEnabled) {
await super.updateAllUnspents();
return;
}
await getStub();
// add the mweb unspents to the list:
List<BitcoinUnspent> mwebUnspentCoins = [];
@ -709,6 +764,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
mwebUnspentCoins.add(unspent);
});
// copy coin control attributes to mwebCoins:
await updateCoins(mwebUnspentCoins);
// get regular ltc unspents (this resets unspentCoins):
await super.updateAllUnspents();
// add the mwebCoins:
unspentCoins.addAll(mwebUnspentCoins);
}
@ -718,7 +779,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (!mwebEnabled) {
return balance;
}
await getStub();
// update unspent balances:
await updateUnspent();
@ -888,10 +948,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
tx.isMweb = mwebEnabled;
if (!mwebEnabled) {
tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: false))
.address;
return tx;
}
await waitForMwebAddresses();
await getStub();
final resp = await CwMweb.create(CreateRequest(
rawTx: hex.decode(tx.hex),
@ -912,13 +975,27 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
hasMwebOutput = true;
break;
}
if (output.address.toLowerCase().contains("mweb")) {
hasMwebOutput = true;
break;
}
}
if (tx2.mwebBytes != null && tx2.mwebBytes!.isNotEmpty) {
hasMwebInput = true;
// check if mweb inputs are used:
for (final utxo in tx.utxos) {
if (utxo.utxo.scriptType == SegwitAddresType.mweb) {
hasMwebInput = true;
}
}
bool isPegIn = !hasMwebInput && hasMwebOutput;
bool isRegular = !hasMwebInput && !hasMwebOutput;
tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: isPegIn || isRegular))
.address;
if (!hasMwebInput && !hasMwebOutput) {
tx.isMweb = false;
return tx;
}
@ -969,7 +1046,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final addresses = <String>{};
transaction.inputAddresses?.forEach((id) async {
final utxo = mwebUtxosBox.get(id);
// await mwebUtxosBox.delete(id);// gets deleted in checkMwebUtxosSpent
await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
if (utxo == null) return;
final addressRecord = walletAddresses.allAddresses
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
@ -988,6 +1065,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
print(e);
print(s);
if (e.toString().contains("commit failed")) {
print(e);
throw Exception("Transaction commit failed (no peers responded), please try again.");
}
rethrow;
@ -1000,13 +1078,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
@override
Future<void> close() async {
Future<void> close({required bool shouldCleanup}) async {
_utxoStream?.cancel();
_feeRatesTimer?.cancel();
_syncTimer?.cancel();
_processingTimer?.cancel();
await stopSync();
await super.close();
if (shouldCleanup) {
try {
await stopSync();
} catch (_) {}
}
await super.close(shouldCleanup: shouldCleanup);
}
Future<void> setMwebEnabled(bool enabled) async {
@ -1014,17 +1096,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return;
}
alwaysScan = enabled;
mwebEnabled = enabled;
(walletAddresses as LitecoinWalletAddresses).mwebEnabled = enabled;
await stopSync();
await save();
try {
await stopSync();
} catch (_) {}
await startSync();
}
Future<RpcClient> getStub() async {
_stub = await CwMweb.stub();
return _stub;
}
Future<StatusResponse> getStatusRequest() async {
final resp = await CwMweb.status(StatusRequest());
return resp;
@ -1152,4 +1233,64 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return false;
}
LedgerConnection? _ledgerConnection;
LitecoinLedgerApp? _litecoinLedgerApp;
@override
void setLedgerConnection(LedgerConnection connection) {
_ledgerConnection = connection;
_litecoinLedgerApp =
LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs,
required BigInt fee,
required BasedUtxoNetwork network,
required List<UtxoWithAddress> utxos,
required Map<String, PublicKeyWithDerivationPath> publicKeys,
String? memo,
bool enableRBF = false,
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async {
final readyInputs = <LedgerTransaction>[];
for (final utxo in utxos) {
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
readyInputs.add(LedgerTransaction(
rawTx: rawTx,
outputIndex: utxo.utxo.vout,
ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
// sequence: enableRBF ? 0x1 : 0xffffffff,
sequence: 0xffffffff,
));
}
String? changePath;
for (final output in outputs) {
final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
}
final rawHex = await _litecoinLedgerApp!.createTransaction(
inputs: readyInputs,
outputs: outputs
.map((e) => TransactionOutput.fromBigInt(
(e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
.toList(),
changePath: changePath,
sigHashType: 0x01,
additionals: ["bech32"],
isSegWit: true,
useTrustedInputForSegwit: true
);
return BtcTransaction.fromRaw(rawHex);
}
}

View file

@ -1,9 +1,11 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'dart:typed_data';
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_unspent.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -14,14 +16,17 @@ import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart';
class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
with Store {
LitecoinWalletAddressesBase(
WalletInfo walletInfo, {
required super.mainHd,
required super.sideHd,
required super.network,
required super.isHardwareWallet,
required this.mwebHd,
required this.mwebEnabled,
super.initialAddresses,
@ -35,18 +40,20 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
print("initialized with ${mwebAddrs.length} mweb addresses");
}
final Bip32Slip10Secp256k1 mwebHd;
final Bip32Slip10Secp256k1? mwebHd;
bool mwebEnabled;
int mwebTopUpIndex = 1000;
List<String> mwebAddrs = [];
bool generating = false;
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get scanSecret =>
mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendPubkey =>
mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
@override
Future<void> init() async {
await initMwebAddresses();
if (!isHardwareWallet) await initMwebAddresses();
await super.init();
}
@ -57,21 +64,42 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
}
Future<void> ensureMwebAddressUpToIndexExists(int index) async {
Uint8List scan = Uint8List.fromList(scanSecret);
Uint8List spend = Uint8List.fromList(spendPubkey);
int count = 0;
while (mwebAddrs.length <= (index + 1)) {
final address = await CwMweb.address(scan, spend, mwebAddrs.length);
mwebAddrs.add(address!);
count++;
// sleep for a bit to avoid making the main thread unresponsive:
if (count > 50) {
count = 0;
await Future.delayed(Duration(milliseconds: 100));
}
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return null;
}
Uint8List scan = Uint8List.fromList(scanSecret);
Uint8List spend = Uint8List.fromList(spendPubkey);
if (index < mwebAddresses.length && index < mwebAddrs.length) {
return;
}
while (generating) {
print("generating.....");
// this function was called multiple times in multiple places:
await Future.delayed(const Duration(milliseconds: 100));
}
print("Generating MWEB addresses up to index $index");
generating = true;
try {
while (mwebAddrs.length <= (index + 1)) {
final addresses =
await CwMweb.addresses(scan, spend, mwebAddrs.length, mwebAddrs.length + 50);
print("generated up to index ${mwebAddrs.length}");
// sleep for a bit to avoid making the main thread unresponsive:
await Future.delayed(Duration(milliseconds: 200));
mwebAddrs.addAll(addresses!);
}
} catch (_) {}
generating = false;
print("Done generating MWEB addresses len: ${mwebAddrs.length}");
// ensure mweb addresses are up to date:
// This is the Case if the Litecoin Wallet is a hardware Wallet
if (mwebHd == null) return;
if (mwebAddresses.length < mwebAddrs.length) {
List<BitcoinAddressRecord> addressRecords = mwebAddrs
.asMap()
@ -90,21 +118,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
Future<void> initMwebAddresses() async {
if (mwebAddrs.length < 1000) {
print("Generating MWEB addresses...");
await ensureMwebAddressUpToIndexExists(20);
print("done generating MWEB addresses");
// List<BitcoinAddressRecord> addressRecords = mwebAddrs
// .asMap()
// .entries
// .map((e) => BitcoinAddressRecord(
// e.value,
// index: e.key,
// type: SegwitAddresType.mweb,
// network: network,
// ))
// .toList();
// addMwebAddresses(addressRecords);
// print("added ${addressRecords.length} mweb addresses");
return;
}
}
@ -135,14 +149,15 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
@action
@override
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async {
Future<BitcoinAddressRecord> getChangeAddress(
{List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
// use regular change address on peg in, otherwise use mweb for change address:
if (!mwebEnabled) {
if (!mwebEnabled || isPegIn) {
return super.getChangeAddress();
}
if (outputs != null && utxoDetails != null) {
if (inputs != null && outputs != null) {
// check if this is a PEGIN:
bool outputsToMweb = false;
bool comesFromMweb = false;
@ -154,14 +169,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
outputsToMweb = true;
}
}
// TODO: this doesn't respect coin control because it doesn't know which available inputs are selected
utxoDetails.availableInputs.forEach((element) {
inputs.forEach((element) {
if (!element.isSending || element.isFrozen) {
return;
}
if (element.address.contains("mweb")) {
comesFromMweb = true;
}
});
bool isPegIn = !comesFromMweb && outputsToMweb;
if (isPegIn && mwebEnabled) {
return super.getChangeAddress();
}
@ -174,7 +193,12 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
if (mwebEnabled) {
await ensureMwebAddressUpToIndexExists(1);
return mwebAddrs[0];
return BitcoinAddressRecord(
mwebAddrs[0],
index: 0,
type: SegwitAddresType.mweb,
network: network,
);
}
return super.getChangeAddress();

View file

@ -1,4 +1,5 @@
import 'dart:io';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
import 'package:cw_core/encryption_file_utils.dart';
@ -20,7 +21,7 @@ class LitecoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials,
BitcoinNewWalletCredentials> {
BitcoinRestoreWalletFromHardware> {
LitecoinWalletService(
this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
@ -147,9 +148,23 @@ class LitecoinWalletService extends WalletService<
}
@override
Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) {
throw UnimplementedError(
"Restoring a Litecoin wallet from a hardware wallet is not yet supported!");
Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
{bool? isTestnet}) async {
final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
credentials.walletInfo?.derivationInfo?.derivationPath =
credentials.hwAccountData.derivationPath;
final wallet = await LitecoinWallet(
password: credentials.password!,
xpub: credentials.hwAccountData.xpub,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.save();
await wallet.init();
return wallet;
}
@override

View file

@ -24,6 +24,7 @@ class PendingBitcoinTransaction with PendingTransaction {
this.isSendAll = false,
this.hasTaprootInputs = false,
this.isMweb = false,
this.utxos = const [],
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type;
@ -36,7 +37,9 @@ class PendingBitcoinTransaction with PendingTransaction {
final bool isSendAll;
final bool hasChange;
final bool hasTaprootInputs;
List<UtxoWithAddress> utxos;
bool isMweb;
String? changeAddressOverride;
String? idOverride;
String? hexOverride;
List<String>? outputAddresses;
@ -63,6 +66,9 @@ class PendingBitcoinTransaction with PendingTransaction {
PendingChange? get change {
try {
final change = _tx.outputs.firstWhere((out) => out.isChange);
if (changeAddressOverride != null) {
return PendingChange(changeAddressOverride!, BtcUtils.fromSatoshi(change.amount));
}
return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount));
} catch (_) {
return null;
@ -117,6 +123,8 @@ class PendingBitcoinTransaction with PendingTransaction {
idOverride = resp.txid;
} on GrpcError catch (e) {
throw BitcoinTransactionCommitFailed(errorMessage: e.message);
} catch (e) {
throw BitcoinTransactionCommitFailed(errorMessage: "Unknown error: ${e.toString()}");
}
}

View file

@ -16,10 +16,6 @@ class PSBTTransactionBuild {
for (var i = 0; i < inputs.length; i++) {
final input = inputs[i];
print(input.utxo.isP2tr());
print(input.utxo.isSegwit());
print(input.utxo.isP2shSegwit());
psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
psbt.setInputOutputIndex(i, input.utxo.vout);
psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff);

View file

@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.4.10"
version: "3.6.1"
args:
dependency: transitive
description:
@ -101,6 +101,14 @@ packages:
url: "https://github.com/cake-tech/blockchain_utils"
source: git
version: "3.3.0"
bluez:
dependency: transitive
description:
name: bluez
sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
url: "https://pub.dev"
source: hosted
version: "0.8.2"
boolean_selector:
dependency: transitive
description:
@ -300,6 +308,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.3"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
encrypt:
dependency: transitive
description:
@ -336,10 +352,10 @@ packages:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.0"
version: "7.0.1"
fixnum:
dependency: transitive
description:
@ -361,19 +377,19 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.1+1"
flutter_reactive_ble:
dependency: transitive
description:
name: flutter_reactive_ble
sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_bluetooth:
dependency: transitive
description:
name: flutter_web_bluetooth
sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
url: "https://pub.dev"
source: hosted
version: "0.2.3"
flutter_web_plugins:
dependency: transitive
description: flutter
@ -387,14 +403,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
functional_data:
dependency: transitive
description:
name: functional_data
sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
glob:
dependency: transitive
description:
@ -403,14 +411,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
google_identity_services_web:
dependency: transitive
description:
name: google_identity_services_web
sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6"
url: "https://pub.dev"
source: hosted
version: "0.3.1+4"
googleapis_auth:
dependency: transitive
description:
name: googleapis_auth
sha256: af7c3a3edf9d0de2e1e0a77e994fae0a581c525fa7012af4fa0d4a52ed9484da
sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938
url: "https://pub.dev"
source: hosted
version: "1.4.1"
version: "1.6.0"
graphs:
dependency: transitive
description:
@ -542,29 +558,37 @@ packages:
ledger_bitcoin:
dependency: "direct main"
description:
path: "."
path: "packages/ledger-bitcoin"
ref: HEAD
resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab
url: "https://github.com/cake-tech/ledger-bitcoin"
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
version: "0.0.2"
ledger_flutter:
version: "0.0.3"
ledger_flutter_plus:
dependency: "direct main"
description:
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:
name: ledger_usb
sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2"
name: ledger_flutter_plus
sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.2.5"
ledger_litecoin:
dependency: "direct main"
description:
path: "packages/ledger-litecoin"
ref: HEAD
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
version: "0.0.2"
ledger_usb_plus:
dependency: transitive
description:
name: ledger_usb_plus
sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
logging:
dependency: transitive
description:
@ -693,6 +717,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
@ -765,30 +797,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.2"
reactive_ble_mobile:
dependency: transitive
description:
name: reactive_ble_mobile
sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
reactive_ble_platform_interface:
dependency: transitive
description:
name: reactive_ble_platform_interface
sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
rxdart:
dependency: "direct main"
description:
name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.27.7"
version: "0.28.0"
shared_preferences:
dependency: "direct main"
description:
@ -809,10 +825,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
url: "https://pub.dev"
source: hosted
version: "2.5.2"
version: "2.5.3"
shared_preferences_linux:
dependency: transitive
description:
@ -979,6 +995,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
universal_ble:
dependency: transitive
description:
name: universal_ble
sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3"
url: "https://pub.dev"
source: hosted
version: "0.12.0"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
unorm_dart:
dependency: transitive
description:
@ -1031,10 +1063,18 @@ packages:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml:
dependency: transitive
description:

View file

@ -24,16 +24,12 @@ dependencies:
git:
url: https://github.com/cake-tech/bitbox-flutter.git
ref: Add-Support-For-OP-Return-data
rxdart: ^0.27.5
rxdart: ^0.28.0
cryptography: ^2.0.5
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2
ledger_flutter: ^1.0.1
ledger_bitcoin:
git:
url: https://github.com/cake-tech/ledger-bitcoin
cw_mweb:
path: ../cw_mweb
grpc: ^3.2.4
@ -44,6 +40,15 @@ dependencies:
bech32:
git:
url: https://github.com/cake-tech/bech32.git
ledger_flutter_plus: ^1.4.1
ledger_bitcoin:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-bitcoin
ledger_litecoin:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-litecoin
dev_dependencies:
flutter_test:
@ -54,10 +59,6 @@ dev_dependencies:
hive_generator: ^1.1.3
dependency_overrides:
ledger_flutter:
git:
url: https://github.com/cake-tech/ledger-flutter.git
ref: cake-v3
watcher: ^1.1.0
protobuf: ^3.1.0
bitcoin_base:

View file

@ -58,6 +58,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
network: network,
initialAddressPageType: addressPageType,
isHardwareWallet: walletInfo.isHardwareWallet,
);
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;

View file

@ -15,6 +15,7 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
required super.mainHd,
required super.sideHd,
required super.network,
required super.isHardwareWallet,
super.initialAddresses,
super.initialRegularAddressIndex,
super.initialChangeAddressIndex,

View file

@ -8,6 +8,7 @@ enum DeviceConnectionType {
[bool isIOS = false]) {
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.polygon:
if (isIOS) return [DeviceConnectionType.ble];

View file

@ -0,0 +1 @@
enum UnspentCoinType { mweb, nonMweb, any }

View file

@ -23,7 +23,7 @@ abstract class WalletAddresses {
return _localAddress ?? address;
}
String? get primaryAddress => null;
String get primaryAddress => address;
String? _localAddress;

View file

@ -83,7 +83,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
Future<void> rescan({required int height});
void close();
Future<void> close({required bool shouldCleanup});
Future<void> changePassword(String password);

View file

@ -2,26 +2,26 @@ import 'dart:async';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_ethereum/ledger_ethereum.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
class EVMChainHardwareWalletService {
EVMChainHardwareWalletService(this.ledger, this.device);
EVMChainHardwareWalletService(this.ledgerConnection);
final Ledger ledger;
final LedgerDevice device;
final LedgerConnection ledgerConnection;
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
final ethereumLedgerApp = EthereumLedgerApp(ledger);
Future<List<HardwareAccountData>> getAvailableAccounts(
{int index = 0, int limit = 5}) async {
final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection);
final version = await ethereumLedgerApp.getVersion(device);
await ethereumLedgerApp.getVersion();
final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
for (final i in indexRange) {
final derivationPath = "m/44'/60'/$i'/0/0";
final address =
await ethereumLedgerApp.getAccounts(device, accountsDerivationPath: derivationPath);
final address = await ethereumLedgerApp.getAccounts(
accountsDerivationPath: derivationPath);
accounts.add(HardwareAccountData(
address: address.first,

View file

@ -1,7 +1,6 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
class EVMChainTransactionCredentials {
EVMChainTransactionCredentials(

View file

@ -264,7 +264,7 @@ abstract class EVMChainWalletBase
}
@override
void close() {
Future<void> close({required bool shouldCleanup}) async {
_client.stop();
_transactionsUpdateTimer?.cancel();
_updateFeesTimer?.cancel();

View file

@ -17,6 +17,9 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
@override
String get primaryAddress => address;
@override
Future<void> init() async {
address = walletInfo.address;

View file

@ -1,17 +1,16 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:cw_core/hardware/device_not_connected_exception.dart';
import 'package:cw_core/hardware/device_not_connected_exception.dart'
as exception;
import 'package:ledger_ethereum/ledger_ethereum.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
class EvmLedgerCredentials extends CredentialsWithKnownAddress {
final String _address;
Ledger? ledger;
LedgerDevice? ledgerDevice;
EthereumLedgerApp? ethereumLedgerApp;
EvmLedgerCredentials(this._address);
@ -19,25 +18,25 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
@override
EthereumAddress get address => EthereumAddress.fromHex(_address);
void setLedger(Ledger setLedger, [LedgerDevice? setLedgerDevice, String? derivationPath]) {
ledger = setLedger;
ledgerDevice = setLedgerDevice;
ethereumLedgerApp =
EthereumLedgerApp(ledger!, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
void setLedgerConnection(LedgerConnection connection,
[String? derivationPath]) {
ethereumLedgerApp = EthereumLedgerApp(connection,
derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
}
@override
MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) =>
throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
MsgSignature signToEcSignature(Uint8List payload,
{int? chainId, bool isEIP1559 = false}) =>
throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
@override
Future<MsgSignature> signToSignature(Uint8List payload,
{int? chainId, bool isEIP1559 = false}) async {
if (ledgerDevice == null && ledger?.devices.isNotEmpty != true) {
throw DeviceNotConnectedException();
if (ethereumLedgerApp == null) {
throw exception.DeviceNotConnectedException();
}
final sig = await ethereumLedgerApp!.signTransaction(device, payload);
final sig = await ethereumLedgerApp!.signTransaction(payload);
final v = sig[0].toInt();
final r = bytesToHex(sig.sublist(1, 1 + 32));
@ -65,14 +64,16 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
}
return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
return MsgSignature(
BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
}
@override
Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async {
if (isNotConnected) throw DeviceNotConnectedException();
Future<Uint8List> signPersonalMessage(Uint8List payload,
{int? chainId}) async {
if (isNotConnected) throw exception.DeviceNotConnectedException();
final sig = await ethereumLedgerApp!.signMessage(device, payload);
final sig = await ethereumLedgerApp!.signMessage(payload);
final r = sig.sublist(1, 1 + 32);
final s = sig.sublist(1 + 32, 1 + 32 + 32);
@ -84,20 +85,22 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
@override
Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List");
throw UnimplementedError(
"EvmLedgerCredentials.signPersonalMessageToUint8List");
Future<void> provideERC20Info(String erc20ContractAddress, int chainId) async {
if (isNotConnected) throw DeviceNotConnectedException();
Future<void> provideERC20Info(
String erc20ContractAddress, int chainId) async {
if (isNotConnected) throw exception.DeviceNotConnectedException();
try {
await ethereumLedgerApp!.getAndProvideERC20TokenInformation(device,
await ethereumLedgerApp!.getAndProvideERC20TokenInformation(
erc20ContractAddress: erc20ContractAddress, chainId: chainId);
} on LedgerException catch (e) {
if (e.errorCode != -28672) rethrow;
} catch (e) {
print(e);
rethrow;
// if (e.errorCode != -28672) rethrow;
}
}
bool get isNotConnected => (ledgerDevice ?? ledger?.devices.firstOrNull) == null;
LedgerDevice get device => ledgerDevice ?? ledger!.devices.first;
bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected;
}

View file

@ -25,20 +25,17 @@ dependencies:
mobx: ^2.0.7+4
cw_core:
path: ../cw_core
ledger_flutter: ^1.0.1
ledger_flutter_plus: ^1.4.1
ledger_ethereum:
git:
url: https://github.com/cake-tech/ledger-ethereum.git
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-ethereum
dependency_overrides:
web3dart:
git:
url: https://github.com/cake-tech/web3dart.git
ref: cake
ledger_flutter:
git:
url: https://github.com/cake-tech/ledger-flutter.git
ref: cake-v3
watcher: ^1.1.0
dev_dependencies:

View file

@ -106,7 +106,7 @@ abstract class HavenWalletBase
Future<void>? updateBalance() => null;
@override
void close() {
Future<void> close({required bool shouldCleanup}) async {
_listener?.stop();
_onAccountChangeReaction?.reaction.dispose();
_autoSaveTimer?.cancel();

View file

@ -116,7 +116,7 @@ class HavenWalletService extends WalletService<
if (!isValid) {
await restoreOrResetWalletFiles(name);
wallet.close();
wallet.close(shouldCleanup: false);
return openWallet(name, password);
}

View file

@ -170,7 +170,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
Future<void>? updateBalance() => null;
@override
void close() async {
Future<void> close({required bool shouldCleanup}) async {
_listener?.stop();
_onAccountChangeReaction?.reaction.dispose();
_onTxHistoryChangeReaction?.reaction.dispose();

View file

@ -29,6 +29,9 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
@override
String get primaryAddress => getAddress(accountIndex: account?.id ?? 0, addressIndex: 0);
@override
String get latestAddress {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;

View file

@ -137,7 +137,7 @@ class MoneroWalletService extends WalletService<
if (!isValid) {
await restoreOrResetWalletFiles(name);
wallet.close();
wallet.close(shouldCleanup: false);
return openWallet(name, password);
}

View file

@ -40,10 +40,17 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
port = null
result.success(null)
} else if (call.method == "address") {
// val scanSecret: ByteArray = call.argument<ByteArray>("scanSecret") ?: ByteArray(0)
// val spendPub: ByteArray = call.argument<ByteArray>("spendPub") ?: ByteArray(0)
// val index: Int = call.argument<Int>("index") ?: 0
// val res = Mwebd.address(scanSecret, spendPub, index)
// result.success(res)
} else if (call.method == "addresses") {
val scanSecret: ByteArray = call.argument<ByteArray>("scanSecret") ?: ByteArray(0)
val spendPub: ByteArray = call.argument<ByteArray>("spendPub") ?: ByteArray(0)
val index: Int = call.argument<Int>("index") ?: 0
val res = Mwebd.address(scanSecret, spendPub, index)
val fromIndex: Int = call.argument<Int>("fromIndex") ?: 0
val toIndex: Int = call.argument<Int>("toIndex") ?: 0
val res = Mwebd.addresses(scanSecret, spendPub, fromIndex, toIndex)
result.success(res)
} else {
result.notImplemented()

View file

@ -32,15 +32,26 @@ public static func register(with registrar: FlutterPluginRegistrar) {
stopServer()
result(nil)
break
case "address":
// case "address":
// let args = call.arguments as! [String: Any]
// let scanSecret = args["scanSecret"] as! FlutterStandardTypedData
// let spendPub = args["spendPub"] as! FlutterStandardTypedData
// let index = args["index"] as! Int32
// let scanSecretData = scanSecret.data
// let spendPubData = spendPub.data
// result(MwebdAddress(scanSecretData, spendPubData, index))
// break
case "addresses":
let args = call.arguments as! [String: Any]
let scanSecret = args["scanSecret"] as! FlutterStandardTypedData
let spendPub = args["spendPub"] as! FlutterStandardTypedData
let index = args["index"] as! Int32
let fromIndex = args["fromIndex"] as! Int32
let toIndex = args["toIndex"] as! Int32
let scanSecretData = scanSecret.data
let spendPubData = spendPub.data
result(MwebdAddress(scanSecretData, spendPubData, index))
result(MwebdAddresses(scanSecretData, spendPubData, fromIndex, toIndex))
break
default:
result(FlutterMethodNotImplemented)

View file

@ -1,3 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:typed_data';
import 'package:grpc/grpc.dart';
@ -10,25 +14,51 @@ class CwMweb {
static ClientChannel? _clientChannel;
static int? _port;
static const TIMEOUT_DURATION = Duration(seconds: 5);
static Timer? logTimer;
static void readFileWithTimer(String filePath) {
final file = File(filePath);
int lastLength = 0;
logTimer?.cancel();
logTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
try {
final currentLength = await file.length();
if (currentLength != lastLength) {
final fileStream = file.openRead(lastLength, currentLength);
final newLines = await fileStream.transform(utf8.decoder).join();
lastLength = currentLength;
log(newLines);
}
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
log('The mwebd debug log probably is not initialized yet.');
}
});
}
static Future<void> _initializeClient() async {
await stop();
// wait a few seconds to make sure the server is stopped
await Future.delayed(const Duration(seconds: 5));
print("_initializeClient() called!");
final appDir = await getApplicationSupportDirectory();
const ltcNodeUri = "45.79.13.180:9333";
const ltcNodeUri = "ltc-electrum.cakewallet.com:9333";
String debugLogPath = "${appDir.path}/logs/debug.log";
readFileWithTimer(debugLogPath);
_port = await CwMwebPlatform.instance.start(appDir.path, ltcNodeUri);
if (_port == null || _port == 0) {
throw Exception("Failed to start server");
}
print("Attempting to connect to server on port: $_port");
log("Attempting to connect to server on port: $_port");
// wait for the server to finish starting up before we try to connect to it:
await Future.delayed(const Duration(seconds: 5));
await Future.delayed(const Duration(seconds: 8));
_clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () {
print("Channel is shutting down!");
_rpcClient = null;
log("Channel is shutting down!");
},
options: const ChannelOptions(
credentials: ChannelCredentials.insecure(),
@ -49,9 +79,18 @@ class CwMweb {
throw Exception("blockTime shouldn't be 0! (this connection is likely broken)");
}
return _rpcClient!;
} catch (e) {
print("Attempt $i failed: $e");
} on GrpcError catch (e) {
log("Attempt $i failed: $e");
log('Caught grpc error: ${e.message}');
_rpcClient = null;
// necessary if the database isn't open:
await stop();
await Future.delayed(const Duration(seconds: 3));
} catch (e) {
log("Attempt $i failed: $e");
_rpcClient = null;
await stop();
await Future.delayed(const Duration(seconds: 3));
}
}
throw Exception("Failed to connect after $maxRetries attempts");
@ -61,22 +100,43 @@ class CwMweb {
try {
await CwMwebPlatform.instance.stop();
await cleanup();
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error stopping server: $e");
log("Error stopping server: $e");
}
}
static Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async {
try {
return CwMwebPlatform.instance.address(scanSecret, spendPub, index);
return (await CwMwebPlatform.instance.addresses(scanSecret, spendPub, index, index + 1))
?.split(',')
.first;
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting address: $e");
return null;
log("Error getting address: $e");
}
return null;
}
static Future<List<String>?> addresses(
Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) async {
try {
return (await CwMwebPlatform.instance.addresses(scanSecret, spendPub, fromIndex, toIndex))
?.split(',');
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
log("Error getting addresses: $e");
}
return null;
}
static Future<void> cleanup() async {
await _clientChannel?.terminate();
try {
await _clientChannel?.terminate();
} catch (_) {}
_rpcClient = null;
_clientChannel = null;
_port = null;
@ -84,51 +144,57 @@ class CwMweb {
// wrappers that handle the connection issues:
static Future<SpentResponse> spent(SpentRequest request) async {
log("mweb.spent() called");
try {
if (_rpcClient == null) {
await _initializeClient();
}
_rpcClient = await stub();
return await _rpcClient!.spent(request, options: CallOptions(timeout: TIMEOUT_DURATION));
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting spent: $e");
return SpentResponse();
log("Error getting spent: $e");
}
return SpentResponse();
}
static Future<StatusResponse> status(StatusRequest request) async {
log("mweb.status() called");
try {
if (_rpcClient == null) {
await _initializeClient();
}
_rpcClient = await stub();
return await _rpcClient!.status(request, options: CallOptions(timeout: TIMEOUT_DURATION));
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting status: $e");
return StatusResponse();
log("Error getting status: $e");
}
return StatusResponse();
}
static Future<CreateResponse> create(CreateRequest request) async {
log("mweb.create() called");
try {
if (_rpcClient == null) {
await _initializeClient();
}
_rpcClient = await stub();
return await _rpcClient!.create(request, options: CallOptions(timeout: TIMEOUT_DURATION));
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting create: $e");
return CreateResponse();
log("Error getting create: $e");
}
return CreateResponse();
}
static Future<ResponseStream<Utxo>?> utxos(UtxosRequest request) async {
log("mweb.utxos() called");
try {
if (_rpcClient == null) {
await _initializeClient();
}
// this is a stream, so we should have an effectively infinite timeout:
return _rpcClient!.utxos(request, options: CallOptions(timeout: const Duration(days: 1000 * 365)));
_rpcClient = await stub();
final resp = _rpcClient!
.utxos(request, options: CallOptions(timeout: const Duration(days: 1000 * 365)));
log("got utxo stream");
return resp;
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting utxos: $e");
return null;
log("Error getting utxos: $e");
}
return null;
}
}

View file

@ -1,3 +1,5 @@
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@ -11,6 +13,9 @@ class MethodChannelCwMweb extends CwMwebPlatform {
@override
Future<int?> start(String dataDir, String nodeUri) async {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return null;
}
final result =
await methodChannel.invokeMethod<int>('start', {'dataDir': dataDir, 'nodeUri': nodeUri});
return result;
@ -18,11 +23,17 @@ class MethodChannelCwMweb extends CwMwebPlatform {
@override
Future<void> stop() async {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return;
}
await methodChannel.invokeMethod<void>('stop');
}
@override
Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return null;
}
final result = await methodChannel.invokeMethod<String>('address', {
'scanSecret': scanSecret,
'spendPub': spendPub,
@ -30,4 +41,18 @@ class MethodChannelCwMweb extends CwMwebPlatform {
});
return result;
}
@override
Future<String?> addresses(Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) async {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return null;
}
final result = await methodChannel.invokeMethod<String>('addresses', {
'scanSecret': scanSecret,
'spendPub': spendPub,
'fromIndex': fromIndex,
'toIndex': toIndex,
});
return result;
}
}

View file

@ -36,4 +36,8 @@ abstract class CwMwebPlatform extends PlatformInterface {
Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) {
throw UnimplementedError('address(int) has not been implemented.');
}
Future<String?> addresses(Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) {
throw UnimplementedError('addresses has not been implemented.');
}
}

View file

@ -149,7 +149,7 @@ abstract class NanoWalletBase
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
@override
void close() {
Future<void> close({required bool shouldCleanup}) async {
_client.stop();
_receiveTimer?.cancel();
}

View file

@ -18,6 +18,9 @@ abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
@override
String get primaryAddress => address;
@observable
NanoAccount? account;

View file

@ -117,10 +117,10 @@ packages:
dependency: "direct overridden"
description:
name: build_runner_core
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e"
url: "https://pub.dev"
source: hosted
version: "7.2.7"
version: "7.2.7+1"
built_collection:
dependency: transitive
description:

View file

@ -179,7 +179,7 @@ abstract class SolanaWalletBase
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
@override
void close() {
Future<void> close({required bool shouldCleanup}) async {
_client.stop();
_transactionsUpdateTimer?.cancel();
}

View file

@ -14,6 +14,9 @@ abstract class SolanaWalletAddressesBase extends WalletAddresses with Store {
@override
String address;
@override
String get primaryAddress => address;
@override
Future<void> init() async {
address = walletInfo.address;

View file

@ -217,7 +217,7 @@ abstract class TronWalletBase
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
@override
void close() => _transactionsUpdateTimer?.cancel();
Future<void> close({required bool shouldCleanup}) async => _transactionsUpdateTimer?.cancel();
@action
@override

View file

@ -17,6 +17,9 @@ abstract class TronWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
@override
String get primaryAddress => address;
@override
Future<void> init() async {
address = walletInfo.address;

View file

@ -160,7 +160,7 @@ abstract class WowneroWalletBase
Future<void>? updateBalance() => null;
@override
void close() async {
Future<void> close({required bool shouldCleanup}) async {
_listener?.stop();
_onAccountChangeReaction?.reaction.dispose();
_onTxHistoryChangeReaction?.reaction.dispose();

View file

@ -29,6 +29,9 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
@observable
String address;
@override
String get primaryAddress => getAddress(accountIndex: account?.id ?? 0, addressIndex: 0);
@override
String get latestAddress {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;

View file

@ -134,7 +134,7 @@ class WowneroWalletService extends WalletService<
if (!isValid) {
await restoreOrResetWalletFiles(name);
wallet.close();
wallet.close(shouldCleanup: false);
return openWallet(name, password);
}

View file

@ -5,7 +5,7 @@
**N:B** Throughout this guide, `walletx` refers to the specific wallet type you want to add. If you're adding `BNB` to CakeWallet, then `walletx` for you here is `bnb`.
**Core Folder/Files Setup**
- Idenitify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
- Identify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
- Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`.
- Fill out the necessary information in the various functions in the files, concerning the wallet name, the native currency type, symbol etc.
- Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`.
@ -144,7 +144,7 @@ You can add as many node entries as desired.
}
}
- Next, well write the function to change walletX current node to default. An handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, well use it to identify the current node id.
- Next, well write the function to change walletX current node to default. A handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, well use it to identify the current node id.
Future<void> changeWalletXCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
@ -228,7 +228,7 @@ Now you can run the codebase and successfully create a wallet for type walletX s
**Balance Screen**
- Go to `lib/view_model/dashboard/balance_view_model.dart`
- Modify the function to adjust the way the balance is being display on the app: `isHomeScreenSettingsEnabled`
- Modify the function to adjust the way the balance is being displayed on the app: `isHomeScreenSettingsEnabled`
- Add a case to the `availableBalanceLabel` getter to modify the text being displayed (Available or confirmed)
- Same for `additionalBalanceLabel`
- Next, go to `lib/reactions/fiat_rate_update.dart`

View file

@ -57,7 +57,7 @@ Proceed into the source code before proceeding with the next steps:
### 7. Execute Build & Setup Commands for Cake Wallet
We need to generate project settings like app name, app icon, package name, etc. For this need to setup environment variables and configure project files.
We need to generate project settings like app name, app icon, package name, etc. For this, we need to setup environment variables and configure project files.
Please pick what app you want to build: cakewallet or monero.com.
@ -92,7 +92,7 @@ Then we need to generate localization files and mobx models.
`$ flutter build ios --release`
Then you can open `ios/Runner.xcworkspace` with Xcode and you can to archive the application.
Then you can open `ios/Runner.xcworkspace` with Xcode and you can archive the application.
Or if you want to run to connected device:

View file

@ -22,7 +22,7 @@ Then install `Desktop development with C++` packages via Visual Studio 2022, or
- `C++ 2022 Redistributable Update`
- `C++ core desktop features`
- `MVC v143 - VS 2022 C++ x64/x86 build tools`
- `C++ CMake tools for Windwos`
- `C++ CMake tools for Windows`
- `Testing tools core features - Build Tools`
- `C++ AddressSanitizer`.
@ -38,7 +38,7 @@ For building monero dependencies, it is required to install Windows WSL (https:/
### 5. Pull Cake Wallet source code
You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git:
You can download CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git:
`$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart`
OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip)
@ -52,6 +52,6 @@ For that you need to run the shell (bash - typically same named utility should b
To configure the application, open the directory where you have downloaded or unarchived Cake Wallet sources and run `cakewallet.bat`.
Or if you used WSL and have active shell session you can run `$ ./cakewallet.sh` script in `scripts/windows` which will run `cakewallet.bat` in WSL.
After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contains `CakeWallet.exe` file and another needed files for run the application. Now you can extract files from `Cake Wallet.zip` archive and run the application.
After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contain `CakeWallet.exe` file and another needed files for run the application. Now you can extract files from `Cake Wallet.zip` archive and run the application.
Copyright (c) 2024 Cake Labs LLC.

View file

@ -7,7 +7,38 @@ PODS:
- Flutter
- ReachabilitySwift
- CryptoSwift (1.8.2)
- cw_haven (0.0.1):
- cw_haven/Boost (= 0.0.1)
- cw_haven/Haven (= 0.0.1)
- cw_haven/OpenSSL (= 0.0.1)
- cw_haven/Sodium (= 0.0.1)
- cw_shared_external
- Flutter
- cw_haven/Boost (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/Haven (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/OpenSSL (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/Sodium (0.0.1):
- cw_shared_external
- Flutter
- cw_mweb (0.0.1):
- Flutter
- cw_shared_external (0.0.1):
- cw_shared_external/Boost (= 0.0.1)
- cw_shared_external/OpenSSL (= 0.0.1)
- cw_shared_external/Sodium (= 0.0.1)
- Flutter
- cw_shared_external/Boost (0.0.1):
- Flutter
- cw_shared_external/OpenSSL (0.0.1):
- Flutter
- cw_shared_external/Sodium (0.0.1):
- Flutter
- device_display_brightness (0.0.1):
- Flutter
- device_info_plus (0.0.1):
@ -96,7 +127,7 @@ PODS:
- FlutterMacOS
- sp_scanner (0.0.1):
- Flutter
- SwiftProtobuf (1.26.0)
- SwiftProtobuf (1.27.1)
- SwiftyGif (5.4.5)
- Toast (4.1.1)
- uni_links (0.0.1):
@ -112,7 +143,9 @@ DEPENDENCIES:
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- CryptoSwift
- cw_haven (from `.symlinks/plugins/cw_haven/ios`)
- cw_mweb (from `.symlinks/plugins/cw_mweb/ios`)
- cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
@ -125,7 +158,6 @@ DEPENDENCIES:
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
@ -158,8 +190,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/barcode_scan2/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
cw_haven:
:path: ".symlinks/plugins/cw_haven/ios"
cw_mweb:
:path: ".symlinks/plugins/cw_mweb/ios"
cw_shared_external:
:path: ".symlinks/plugins/cw_shared_external/ios"
device_display_brightness:
:path: ".symlinks/plugins/device_display_brightness/ios"
device_info_plus:
@ -184,8 +220,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/in_app_review/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
package_info:
:path: ".symlinks/plugins/package_info/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
@ -215,7 +249,9 @@ SPEC CHECKSUMS:
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
cw_mweb: 87af74f9659fed0c1a2cbfb44413f1070e79e3ae
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
devicelocale: b22617f40038496deffba44747101255cee005b0
@ -243,7 +279,7 @@ SPEC CHECKSUMS:
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
SwiftProtobuf: 5e8349171e7c2f88f5b9e683cb3cb79d1dc780b3
SwiftProtobuf: b109bd17979d7993a84da14b1e1fdd8b0ded934a
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a

View file

@ -14,12 +14,12 @@
2193F104374FA2746CE8945B /* ResourceHelper.swift in Resources */ = {isa = PBXBuildFile; fileRef = 78D25C60B94E9D9E48D52E5E /* ResourceHelper.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
495FEFF9B395392FED3425DE /* TaskProtocol.swift in Resources */ = {isa = PBXBuildFile; fileRef = 0F42D8065219E0653321EE2B /* TaskProtocol.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
4DFD1BB54A3A50573E19A583 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C663361C56EBB242598F609 /* Pods_Runner.framework */; };
525A2200C6C2A43EDC5C8FC5 /* BreezSDKConnector.swift in Resources */ = {isa = PBXBuildFile; fileRef = 1FB06A93B13D606F06B3924D /* BreezSDKConnector.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
6909E1D79C9986ADF2DE41E9 /* LnurlPayInvoice.swift in Resources */ = {isa = PBXBuildFile; fileRef = DCEA540E3586164FB47AD13E /* LnurlPayInvoice.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
724FDA327BF191BC29DCAA2E /* Constants.swift in Resources */ = {isa = PBXBuildFile; fileRef = 0CCA7ADAD6FF9185EBBB2BCA /* Constants.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
73138617307FA4F838D21D62 /* ServiceLogger.swift in Resources */ = {isa = PBXBuildFile; fileRef = F42258C3697CFE3C8C8D1933 /* ServiceLogger.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
8B1F4FCAA5EB9F3A83D32D5F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7CD6B6020744E8FA471915D /* Pods_Runner.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@ -50,6 +50,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
014D7E4DBCFD76DDE652A4D9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
0C400E0F25B21ABB0025E469 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
0C44A7192518EF8000B570ED /* decrypt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = decrypt.swift; sourceTree = "<group>"; };
0C50DFB82BF3CB56002B0EB3 /* MoneroWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MoneroWallet.framework; sourceTree = "<group>"; };
@ -57,13 +58,11 @@
0C9D68C8264854B60011B691 /* secRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = secRandom.swift; sourceTree = "<group>"; };
0CCA7ADAD6FF9185EBBB2BCA /* Constants.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Constants.swift"; sourceTree = "<group>"; };
0F42D8065219E0653321EE2B /* TaskProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TaskProtocol.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/TaskProtocol.swift"; sourceTree = "<group>"; };
11F9FC13F9EE2A705B213FA9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1F083F2041D1F553F2AF8B62 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
1FB06A93B13D606F06B3924D /* BreezSDKConnector.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BreezSDKConnector.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/BreezSDKConnector.swift"; sourceTree = "<group>"; };
28F61114229803070973270D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3C663361C56EBB242598F609 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
41102141140E57B1DC27FBA1 /* SDKNotificationService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SDKNotificationService.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/SDKNotificationService.swift"; sourceTree = "<group>"; };
58C22CBD8C22B9D6023D59F8 /* LnurlPayInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LnurlPayInfo.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/LnurlPayInfo.swift"; sourceTree = "<group>"; };
5AFFEBFC279AD49C00F906A4 /* wakeLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = wakeLock.swift; sourceTree = "<group>"; };
@ -83,11 +82,12 @@
9D2F2C9F2555316C95EE7EA3 /* RedeemSwap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedeemSwap.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/RedeemSwap.swift"; sourceTree = "<group>"; };
9F46EE5D2BC11178009318F5 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
ABD6FCBB0F4244B090459128 /* BreezSDK.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BreezSDK.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/BreezSDK.swift"; sourceTree = "<group>"; };
AD0937B0140D5A4C24E73BEA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServiceConfig.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/ServiceConfig.swift"; sourceTree = "<group>"; };
C58D93382C00FAC6004BCF69 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WowneroWallet.framework; sourceTree = "<group>"; };
CEAFE49D2C539250009FF3AD /* Mwebd.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Mwebd.xcframework; sourceTree = "<group>"; };
D139E30AEB36740C21C00A9E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
D7CD6B6020744E8FA471915D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DCEA540E3586164FB47AD13E /* LnurlPayInvoice.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LnurlPayInvoice.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/LnurlPayInvoice.swift"; sourceTree = "<group>"; };
F42258C3697CFE3C8C8D1933 /* ServiceLogger.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServiceLogger.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/ServiceLogger.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -97,8 +97,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4DFD1BB54A3A50573E19A583 /* Pods_Runner.framework in Frameworks */,
CEAFE4A02C53926F009FF3AD /* libresolv.tbd in Frameworks */,
8B1F4FCAA5EB9F3A83D32D5F /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -111,7 +111,7 @@
CEAFE49D2C539250009FF3AD /* Mwebd.xcframework */,
C58D93382C00FAC6004BCF69 /* libresolv.tbd */,
0C9986A3251A932F00D566FD /* CryptoSwift.framework */,
3C663361C56EBB242598F609 /* Pods_Runner.framework */,
D7CD6B6020744E8FA471915D /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -137,10 +137,10 @@
84389F1A05D5860790D82820 /* Pods */ = {
isa = PBXGroup;
children = (
11F9FC13F9EE2A705B213FA9 /* Pods-Runner.debug.xcconfig */,
1F083F2041D1F553F2AF8B62 /* Pods-Runner.release.xcconfig */,
AD0937B0140D5A4C24E73BEA /* Pods-Runner.profile.xcconfig */,
0B80439B9064C9708DDB0ADA /* breez_sdk-OnDemandResources */,
014D7E4DBCFD76DDE652A4D9 /* Pods-Runner.debug.xcconfig */,
28F61114229803070973270D /* Pods-Runner.release.xcconfig */,
D139E30AEB36740C21C00A9E /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@ -222,14 +222,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
B91154210ADCED81FBF06A85 /* [CP] Check Pods Manifest.lock */,
11278EDF4D5DB437B3FDB787 /* [CP] Check Pods Manifest.lock */,
CE5E8A222BEE19C700608EA1 /* CopyFiles */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
32D0076A9969C0C38D68AF62 /* [CP] Embed Pods Frameworks */,
F6F67323547956BC4F7B67F1 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -305,21 +305,26 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
32D0076A9969C0C38D68AF62 /* [CP] Embed Pods Frameworks */ = {
11278EDF4D5DB437B3FDB787 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
@ -353,26 +358,21 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
};
B91154210ADCED81FBF06A85 /* [CP] Check Pods Manifest.lock */ = {
F6F67323547956BC4F7B67F1 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */

View file

@ -110,12 +110,12 @@ import workmanager
private func makeSecure() {
if (!self.window.subviews.contains(textField)) {
let view = UIView(frame: CGRect(x: 0, y: 0, width: textField.frame.self.width, height: textField.frame.self.height))
self.window.addSubview(textField)
textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
self.window.layer.superlayer?.addSublayer(textField.layer)
textField.layer.sublayers?.first?.addSublayer(self.window.layer)
textField.layer.sublayers?.last!.addSublayer(self.window.layer)
textField.leftView = view
textField.leftViewMode = .always
}
}
}
}

View file

@ -106,34 +106,33 @@ class CWBitcoin extends Bitcoin {
}
@override
Object createBitcoinTransactionCredentials(List<Output> outputs,
{required TransactionPriority priority, int? feeRate}) {
Object createBitcoinTransactionCredentials(
List<Output> outputs, {
required TransactionPriority priority,
int? feeRate,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) {
final bitcoinFeeRate =
priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null;
return BitcoinTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo))
.toList(),
priority: priority as BitcoinTransactionPriority,
feeRate: bitcoinFeeRate);
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo))
.toList(),
priority: priority as BitcoinTransactionPriority,
feeRate: bitcoinFeeRate,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
}
@override
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs,
{TransactionPriority? priority, required int feeRate}) =>
BitcoinTransactionCredentials(outputs,
priority: priority != null ? priority as BitcoinTransactionPriority : null,
feeRate: feeRate);
@override
@computed
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
@ -205,9 +204,19 @@ class CWBitcoin extends Bitcoin {
(priority as BitcoinTransactionPriority).labelWithRate(rate, customRate);
@override
List<BitcoinUnspent> getUnspents(Object wallet) {
List<BitcoinUnspent> getUnspents(Object wallet,
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.unspentCoins;
return bitcoinWallet.unspentCoins.where((element) {
switch (coinTypeToSpendFrom) {
case UnspentCoinType.mweb:
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
case UnspentCoinType.nonMweb:
return element.bitcoinAddressRecord.type != SegwitAddresType.mweb;
case UnspentCoinType.any:
return true;
}
}).toList();
}
Future<void> updateUnspents(Object wallet) async {
@ -262,7 +271,14 @@ class CWBitcoin extends Bitcoin {
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
@override
List<ReceivePageOption> getLitecoinReceivePageOptions() => BitcoinReceivePageOption.allLitecoin;
List<ReceivePageOption> getLitecoinReceivePageOptions() {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return BitcoinReceivePageOption.allLitecoin
.where((element) => element != BitcoinReceivePageOption.mweb)
.toList();
}
return BitcoinReceivePageOption.allLitecoin;
}
@override
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option) {
@ -382,19 +398,21 @@ class CWBitcoin extends Bitcoin {
final history = await electrumClient.getHistory(sh);
final balance = await electrumClient.getBalance(sh);
dInfoCopy.balance = balance.entries.first.value.toString();
dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
dInfoCopy.address = address;
dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy);
} catch (e) {
print(e);
} catch (e, s) {
print("derivationInfoError: $e");
print("derivationInfoStack: $s");
}
}
}
// sort the list such that derivations with the most transactions are first:
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
return list;
}
@ -469,18 +487,30 @@ class CWBitcoin extends Bitcoin {
}
@override
void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
(wallet as BitcoinWallet).setLedger(ledger, device);
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
(wallet as ElectrumWallet).setLedgerConnection(connection);
}
@override
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async {
final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection);
try {
return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} on LedgerException catch (err) {
print(err.message);
} catch (err) {
print(err);
throw err;
}
}
@override
Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async {
final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection);
try {
return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} catch (err) {
print(err);
throw err;
}
}
@ -608,7 +638,7 @@ class CWBitcoin extends Bitcoin {
final updatedOutputs = outputs.map((output) {
try {
final pendingOut = pendingTx!.outputs[outputs.indexOf(output)];
final pendingOut = pendingTx.outputs[outputs.indexOf(output)];
final updatedOutput = output;
updatedOutput.stealthAddress = P2trAddress.fromScriptPubkey(script: pendingOut.scriptPubKey)
@ -658,11 +688,22 @@ class CWBitcoin extends Bitcoin {
String? getUnusedMwebAddress(Object wallet) {
try {
final electrumWallet = wallet as ElectrumWallet;
final walletAddresses = electrumWallet.walletAddresses as ElectrumWalletAddresses;
final mwebAddress = walletAddresses.mwebAddresses.firstWhere((element) => !element.isUsed);
final mwebAddress =
electrumWallet.walletAddresses.mwebAddresses.firstWhere((element) => !element.isUsed);
return mwebAddress.address;
} catch (_) {
return null;
}
}
String? getUnusedSegwitAddress(Object wallet) {
try {
final electrumWallet = wallet as ElectrumWallet;
final segwitAddress = electrumWallet.walletAddresses.allAddresses
.firstWhere((element) => !element.isUsed && element.type == SegwitAddresType.p2wpkh);
return segwitAddress.address;
} catch (_) {
return null;
}
}
}

View file

@ -36,9 +36,9 @@ class AddressValidator extends TextValidator {
'|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}';
case CryptoCurrency.btc:
pattern =
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${RegExp(r'(bc|tb)1q[ac-hj-np-z02-9]{25,39}}').pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
case CryptoCurrency.ltc:
pattern = '^${P2wpkhAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$';
pattern = '^${RegExp(r'ltc1q[ac-hj-np-z02-9]{25,39}').pattern}\$|^${MwebAddress.regex.pattern}\$';
case CryptoCurrency.nano:
pattern = '[0-9a-zA-Z_]+';
case CryptoCurrency.banano:

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/get_encryption_key.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cw_core/root_dir.dart';
@ -105,7 +106,15 @@ class BackupService {
if (entity.path == archivePath || entity.path == tmpDir.path) {
return;
}
final filename = entity.absolute;
for (var ignore in ignoreFiles) {
final filename = entity.absolute.path;
if (filename.endsWith(ignore) && !filename.contains("wallets/")) {
print("ignoring backup file: $filename");
return;
}
}
print("restoring: $filename");
if (entity.statSync().type == FileSystemEntityType.directory) {
zipEncoder.addDirectory(Directory(entity.path));
} else {
@ -148,14 +157,29 @@ class BackupService {
await _importPreferencesDump();
}
// checked with .endsWith - so this should be the last part of the filename
static const ignoreFiles = [
"flutter_assets/kernel_blob.bin",
"flutter_assets/vm_snapshot_data",
"flutter_assets/isolate_snapshot_data",
".lock",
];
Future<void> _importBackupV2(Uint8List data, String password) async {
final appDir = await getAppDir();
final decryptedData = await _decryptV2(data, password);
final zip = ZipDecoder().decodeBytes(decryptedData);
outer:
for (var file in zip.files) {
final filename = file.name;
for (var ignore in ignoreFiles) {
if (filename.endsWith(ignore) && !filename.contains("wallets/")) {
print("ignoring backup file: $filename");
continue outer;
}
}
print("restoring: $filename");
if (file.isFile) {
final content = file.content as List<int>;
File('${appDir.path}/' + filename)
@ -206,6 +230,16 @@ class BackupService {
json.decode(transactionDescriptionFile.readAsStringSync()) as Map<String, dynamic>;
final descriptionsMap = jsonData.map((key, value) =>
MapEntry(key, TransactionDescription.fromJson(value as Map<String, dynamic>)));
if (!_transactionDescriptionBox.isOpen) {
final transactionDescriptionsBoxKey = await getEncryptionKey(secureStorage: secureStorageShared, forKey: TransactionDescription.boxKey);
final transactionDescriptionBox = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey,
);
await transactionDescriptionBox.putAll(descriptionsMap);
return;
}
await _transactionDescriptionBox.putAll(descriptionsMap);
}

View file

@ -167,6 +167,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/transaction_info.dart';
@ -382,7 +383,11 @@ Future<void> setup({
getIt.registerFactory<NewWalletTypeViewModel>(() => NewWalletTypeViewModel(_walletInfoSource));
getIt.registerFactory<WalletManager>(
() => WalletManager(_walletInfoSource, getIt.get<SharedPreferences>()),
() {
final instance = WalletManager(_walletInfoSource, getIt.get<SharedPreferences>());
instance.updateWalletGroups();
return instance;
},
);
getIt.registerFactoryParam<WalletGroupsDisplayViewModel, WalletType, void>(
@ -715,8 +720,8 @@ Future<void> setup({
getIt.get<SendTemplateStore>(),
getIt.get<FiatConversionStore>()));
getIt.registerFactory<SendViewModel>(
() => SendViewModel(
getIt.registerFactoryParam<SendViewModel, UnspentCoinType?, void>(
(coinTypeToSpendFrom, _) => SendViewModel(
getIt.get<AppStore>(),
getIt.get<SendTemplateViewModel>(),
getIt.get<FiatConversionStore>(),
@ -724,12 +729,13 @@ Future<void> setup({
getIt.get<ContactListViewModel>(),
_transactionDescriptionBox,
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
),
);
getIt.registerFactoryParam<SendPage, PaymentRequest?, void>(
(PaymentRequest? initialPaymentRequest, _) => SendPage(
sendViewModel: getIt.get<SendViewModel>(),
getIt.registerFactoryParam<SendPage, PaymentRequest?, UnspentCoinType?>(
(PaymentRequest? initialPaymentRequest, coinTypeToSpendFrom) => SendPage(
sendViewModel: getIt.get<SendViewModel>(param1: coinTypeToSpendFrom),
authService: getIt.get<AuthService>(),
initialPaymentRequest: initialPaymentRequest,
));
@ -1203,14 +1209,21 @@ Future<void> setup({
getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>()));
getIt.registerFactory(() {
getIt.registerFactoryParam<UnspentCoinsListViewModel, UnspentCoinType?, void>(
(coinTypeToSpendFrom, _) {
final wallet = getIt.get<AppStore>().wallet;
return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource);
return UnspentCoinsListViewModel(
wallet: wallet!,
unspentCoinsInfo: _unspentCoinsInfoSource,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
);
});
getIt.registerFactory(() =>
UnspentCoinsListPage(unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>()));
getIt.registerFactoryParam<UnspentCoinsListPage, UnspentCoinType?, void>(
(coinTypeToSpendFrom, _) => UnspentCoinsListPage(
unspentCoinsListViewModel:
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom)));
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel, UnspentCoinsItem,
UnspentCoinsListViewModel>(

View file

@ -254,6 +254,10 @@ Future<void> defaultSettingsMigration(
case 41:
_deselectQuantex(sharedPreferences);
await _addSethNode(nodes, sharedPreferences);
await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 42:
updateBtcElectrumNodeToUseSSL(nodes, sharedPreferences);
break;
default:
break;
@ -269,6 +273,15 @@ Future<void> defaultSettingsMigration(
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
}
void updateBtcElectrumNodeToUseSSL(Box<Node> nodes, SharedPreferences sharedPreferences) {
final btcElectrumNode = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletBitcoinUri);
if (btcElectrumNode != null) {
btcElectrumNode.useSSL = true;
btcElectrumNode.save();
}
}
void _deselectQuantex(SharedPreferences sharedPreferences) {
final Map<String, dynamic> exchangeProvidersSelection =
json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
@ -898,7 +911,9 @@ Future<void> changeDefaultBitcoinNode(
final newCakeWalletBitcoinNode =
Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(newCakeWalletBitcoinNode);
if (!nodeSource.values.any((element) => element.uriRaw == newCakeWalletBitcoinUri)) {
await nodeSource.add(newCakeWalletBitcoinNode);
}
if (needToReplaceCurrentBitcoinNode) {
await sharedPreferences.setInt(
@ -930,6 +945,10 @@ Future<void> _addBitcoinNode({
bool replaceExisting = false,
bool useSSL = false,
}) async {
bool isNodeExists = nodeSource.values.any((element) => element.uriRaw == nodeUri);
if (isNodeExists) {
return;
}
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
final currentBitcoinNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
@ -1317,3 +1336,16 @@ Future<void> removeMoneroWorld(
await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
}
}
Future<void> updateTronNodesWithNowNodes({
required SharedPreferences sharedPreferences,
required Box<Node> nodes,
}) async {
final tronNowNodesUri = 'trx.nownodes.io';
if (nodes.values.any((node) => node.uriRaw == tronNowNodesUri)) return;
await nodes.add(Node(uri: tronNowNodesUri, type: WalletType.tron));
await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
}

View file

@ -0,0 +1,65 @@
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
class HardwareWalletDevice {
final String name;
final HardwareWalletDeviceType type;
final HardwareWalletConnectionType connectionType;
const HardwareWalletDevice({
required this.name,
required this.type,
required this.connectionType,
});
factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) =>
HardwareWalletDevice(
name: device.name,
type: device.deviceInfo.toGeneric(),
connectionType: device.connectionType.toGeneric(),
);
}
enum HardwareWalletDeviceType {
ledgerBlue,
ledgerNanoS,
ledgerNanoX,
ledgerNanoSPlus,
ledgerStax,
ledgerFlex;
}
enum HardwareWalletConnectionType {
usb,
ble,
nfc;
}
extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
HardwareWalletDeviceType toGeneric() {
switch (this) {
case ledger.LedgerDeviceType.blue:
return HardwareWalletDeviceType.ledgerBlue;
case ledger.LedgerDeviceType.nanoS:
return HardwareWalletDeviceType.ledgerNanoS;
case ledger.LedgerDeviceType.nanoSP:
return HardwareWalletDeviceType.ledgerNanoSPlus;
case ledger.LedgerDeviceType.nanoX:
return HardwareWalletDeviceType.ledgerNanoX;
case ledger.LedgerDeviceType.stax:
return HardwareWalletDeviceType.ledgerStax;
case ledger.LedgerDeviceType.flex:
return HardwareWalletDeviceType.ledgerFlex;
}
}
}
extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
HardwareWalletConnectionType toGeneric() {
switch (this) {
case ledger.ConnectionType.usb:
return HardwareWalletConnectionType.usb;
case ledger.ConnectionType.ble:
return HardwareWalletConnectionType.ble;
}
}
}

View file

@ -191,21 +191,21 @@ class CWEthereum extends Ethereum {
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger(
ledger,
device.connectionType == ConnectionType.usb ? device : null,
wallet.walletInfo.derivationInfo?.derivationPath);
void setLedgerConnection(
WalletBase wallet, ledger.LedgerConnection connection) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
.setLedgerConnection(
connection, wallet.walletInfo.derivationInfo?.derivationPath);
}
@override
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async {
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
try {
return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} on LedgerException catch (err) {
print(err.message);
} catch (err) {
print(err);
throw err;
}
}

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/app_scroll_behavior.dart';
import 'package:cake_wallet/buy/order.dart';
@ -43,6 +44,7 @@ import 'package:hive/hive.dart';
import 'package:cw_core/root_dir.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_core/window_size.dart';
import 'package:logging/logging.dart';
final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>();
@ -68,8 +70,18 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
};
await initializeAppAtRoot();
runApp(App(key: topLevelKey));
if (kDebugMode) {
final appDocDir = await getAppDir();
final ledgerFile = File('${appDocDir.path}/ledger_log.txt');
if (!ledgerFile.existsSync()) ledgerFile.createSync();
Logger.root.onRecord.listen((event) async {
final content = ledgerFile.readAsStringSync();
ledgerFile.writeAsStringSync("$content\n${event.message}");
});
}
runApp(App(key: topLevelKey));
isAppRunning = true;
}, (error, stackTrace) async {
if (!isAppRunning) {
@ -192,7 +204,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 41,
initialMigrationVersion: 42,
);
}

View file

@ -190,20 +190,21 @@ class CWPolygon extends Polygon {
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger(
ledger,
device.connectionType == ConnectionType.usb ? device : null,
wallet.walletInfo.derivationInfo?.derivationPath);
void setLedgerConnection(
WalletBase wallet, ledger.LedgerConnection connection) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
.setLedgerConnection(
connection, wallet.walletInfo.derivationInfo?.derivationPath);
}
@override
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async {
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device);
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
try {
return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} on LedgerException catch (err) {
} catch (err) {
print(err);
throw err;
}
}

View file

@ -120,6 +120,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
@ -184,7 +185,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
final type = settings.arguments as WalletType;
final walletGroupsDisplayVM = getIt.get<WalletGroupsDisplayViewModel>(param1: type);
return CupertinoPageRoute<void>(builder: (_) => WalletGroupsDisplayPage(walletGroupsDisplayVM));
return CupertinoPageRoute<void>(
builder: (_) => WalletGroupsDisplayPage(walletGroupsDisplayVM));
case Routes.newWallet:
final args = settings.arguments as NewWalletArguments;
@ -348,13 +350,17 @@ Route<dynamic> createRoute(RouteSettings settings) {
settings: settings, builder: (_) => getIt.get<DashboardPage>());
case Routes.send:
final initialPaymentRequest = settings.arguments as PaymentRequest?;
final args = settings.arguments as Map<String, dynamic>?;
final initialPaymentRequest = args?['paymentRequest'] as PaymentRequest?;
final coinTypeToSpendFrom = args?['coinTypeToSpendFrom'] as UnspentCoinType?;
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
));
fullscreenDialog: true,
builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
param2: coinTypeToSpendFrom,
),
);
case Routes.sendTemplate:
return CupertinoPageRoute<void>(
@ -604,7 +610,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>());
case Routes.unspentCoinsList:
return MaterialPageRoute<void>(builder: (_) => getIt.get<UnspentCoinsListPage>());
final coinTypeToSpendFrom = settings.arguments as UnspentCoinType?;
return MaterialPageRoute<void>(
builder: (_) => getIt.get<UnspentCoinsListPage>(param1: coinTypeToSpendFrom));
case Routes.unspentCoinsDetails:
final args = settings.arguments as List;
@ -778,7 +786,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.walletGroupDescription:
final walletType = settings.arguments as WalletType;
return MaterialPageRoute<void>(
builder: (_) => WalletGroupDescriptionPage(
selectedWalletType: walletType,

View file

@ -3,15 +3,14 @@ import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/debug_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
@ -19,7 +18,8 @@ class ConnectDevicePageParams {
final WalletType walletType;
final OnConnectDevice onConnectDevice;
ConnectDevicePageParams({required this.walletType, required this.onConnectDevice});
ConnectDevicePageParams(
{required this.walletType, required this.onConnectDevice});
}
class ConnectDevicePage extends BasePage {
@ -35,7 +35,8 @@ class ConnectDevicePage extends BasePage {
String get title => S.current.restore_title_from_hardware_wallet;
@override
Widget body(BuildContext context) => ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
Widget body(BuildContext context) =>
ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
}
class ConnectDevicePageBody extends StatefulWidget {
@ -43,47 +44,35 @@ class ConnectDevicePageBody extends StatefulWidget {
final OnConnectDevice onConnectDevice;
final LedgerViewModel ledgerVM;
const ConnectDevicePageBody(this.walletType, this.onConnectDevice, this.ledgerVM);
const ConnectDevicePageBody(
this.walletType, this.onConnectDevice, this.ledgerVM);
@override
ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
}
class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
final imageLedger = 'assets/images/ledger_nano.png';
final ledger = Ledger(
options: LedgerOptions(
scanMode: ScanMode.balanced,
maxScanDuration: const Duration(minutes: 5),
),
onPermissionRequest: (_) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.bluetoothAdvertise,
].request();
return statuses.values.where((status) => status.isDenied).isEmpty;
},
);
var bleIsEnabled = true;
var bleDevices = <LedgerDevice>[];
var usbDevices = <LedgerDevice>[];
late Timer? _usbRefreshTimer = null;
late Timer? _bleRefreshTimer = null;
late Timer? _bleStateTimer = null;
late StreamSubscription<LedgerDevice>? _bleRefresh = null;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
_bleStateTimer = Timer.periodic(
Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState());
_bleRefreshTimer =
Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
if (Platform.isAndroid) {
_usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
_usbRefreshTimer =
Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
}
});
}
@ -91,35 +80,59 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
@override
void dispose() {
_bleRefreshTimer?.cancel();
_bleStateTimer?.cancel();
_usbRefreshTimer?.cancel();
_bleRefresh?.cancel();
super.dispose();
}
Future<void> _refreshUsbDevices() async {
final dev = await ledger.listUsbDevices();
final dev = await widget.ledgerVM.ledgerPlusUSB.devices;
if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
// _usbRefresh = widget.ledgerVM
// .scanForUsbDevices()
// .listen((device) => setState(() => usbDevices.add(device)))
// ..onError((e) {
// throw e.toString();
// });
// Keep polling until the lfp lib gets updated
// _usbRefreshTimer?.cancel();
// _usbRefreshTimer = null;
}
Future<void> _refreshBleDevices() async {
try {
_bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device)))
_bleRefresh = widget.ledgerVM
.scanForBleDevices()
.listen((device) => setState(() => bleDevices.add(device)))
..onError((e) {
throw e.toString();
});
setState(() => bleIsEnabled = true);
_bleRefreshTimer?.cancel();
_bleRefreshTimer = null;
} catch (e) {
setState(() => bleIsEnabled = false);
print(e);
}
}
Future<void> _connectToDevice(LedgerDevice device) async {
await widget.ledgerVM.connectLedger(device);
await widget.ledgerVM.connectLedger(device, widget.walletType);
widget.onConnectDevice(context, widget.ledgerVM);
}
String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {
switch (deviceInfo) {
case LedgerDeviceType.nanoX:
return 'assets/images/hardware_wallet/ledger_nano_x.png';
case LedgerDeviceType.stax:
return 'assets/images/hardware_wallet/ledger_stax.png';
case LedgerDeviceType.flex:
return 'assets/images/hardware_wallet/ledger_flex.png';
default:
return 'assets/images/hardware_wallet/ledger_nano_x.png';
}
}
@override
Widget build(BuildContext context) {
return Center(
@ -139,7 +152,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor),
textAlign: TextAlign.center,
),
),
@ -152,18 +167,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
// title: "Debug Ledger",
// leading: imageLedger,
// ),
if (!bleIsEnabled)
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).ledger_please_enable_bluetooth,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
textAlign: TextAlign.center,
Observer(
builder: (_) => Offstage(
offstage: widget.ledgerVM.bleIsEnabled,
child: Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).ledger_please_enable_bluetooth,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor),
textAlign: TextAlign.center,
),
),
),
),
if (bleDevices.length > 0) ...[
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
@ -174,7 +196,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
),
),
),
@ -186,7 +210,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
leading: imageLedger,
leading: _getDeviceTileLeading(device.deviceInfo),
connectionType: device.connectionType,
),
),
@ -203,7 +227,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
),
),
),
@ -215,7 +241,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
leading: imageLedger,
leading: _getDeviceTileLeading(device.deviceInfo),
connectionType: device.connectionType,
),
),

View file

@ -1,15 +1,15 @@
// import 'dart:convert';
// import 'dart:typed_data';
//
// import 'package:basic_utils/basic_utils.dart';
// import 'package:bitcoin_base/bitcoin_base.dart';
// import 'package:cake_wallet/src/screens/base_page.dart';
// import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
// import 'package:cake_wallet/src/widgets/primary_button.dart';
// import 'package:cake_wallet/utils/responsive_layout_util.dart';
// import 'package:convert/convert.dart';
// import 'package:flutter/material.dart';
// import 'package:ledger_bitcoin/ledger_bitcoin.dart';
// import 'package:ledger_flutter/ledger_flutter.dart';
// import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
// import 'package:ledger_litecoin/ledger_litecoin.dart';
// import 'package:permission_handler/permission_handler.dart';
// import 'package:polyseed/polyseed.dart';
//
// class DebugDevicePage extends BasePage {
// @override
@ -50,7 +50,9 @@
// },
// );
//
// late BitcoinLedgerApp btc;
// // late BitcoinLedgerApp btc;
// late LitecoinLedgerApp ltc;
//
// var devices = <LedgerDevice>[];
// var status = "";
// var counter = 0;
@ -59,7 +61,8 @@
// @override
// void initState() {
// super.initState();
// btc = BitcoinLedgerApp(ledger);
// // btc = BitcoinLedgerApp(ledger);
// ltc = LitecoinLedgerApp(ledger);
// }
//
// @override
@ -81,7 +84,7 @@
//
// @override
// Widget build(BuildContext context) {
// final imageLedger = 'assets/images/ledger_nano.png';
// final imageLedger = 'assets/images/hardware_wallet/ledger_nano_x.png';
//
// return Center(
// child: Container(
@ -99,40 +102,25 @@
// DebugButton(
// title: "Get Version",
// method: "Version",
// func: () async => await btc.getVersion(selectedDevice!),
// ),
// DebugButton(
// title: "Get Master Fingerprint",
// method: "Master Fingerprint",
// func: () async => hex.encode(await btc.getMasterFingerprint(selectedDevice!)),
// ),
// DebugButton(
// title: "Get XPub",
// method: "XPub",
// func: () async => await btc.getXPubKey(selectedDevice!, derivationPath: "m/84'/0'/$counter'"),
// // func: () async => await btc.getVersion(selectedDevice!),
// func: () async => await ltc.getVersion(selectedDevice!),
// ),
// DebugButton(
// title: "Get Wallet Address",
// method: "Wallet Address",
// func: () async {
// setState(() => counter++);
// final derivationPath = "m/84'/0'/$counter'/0/0";
// return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath);
// final derivationPath = "m/84'/2'/0'/0/0";
// return await ltc.getAccounts(selectedDevice!,
// accountsDerivationPath: derivationPath);
// // return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath);
// // return await ethereum!.getHardwareWalletAccounts(selectedDevice!);
// },
// },
// ),
// DebugButton(
// title: "Send Money",
// method: "Sig",
// func: () async {
// final psbt = PsbtV2();
// final psbtBuf = base64.decode(
// "cHNidP8BAgQCAAAAAQQBAQEFAQIAAQ4gTW6k/cwKKu1u7m9oKr5ob7VcAC0IPkfaDitRi/FkD7sBDwQAAAAAARAE/////wEA/ekBAQAAAAABA9AYVQLI722H0osKMa/4dvMucrnKV1Myxtlp0l0BoOBDAQAAAAD/////ku6r2ABaHt9N26f/P4eMljX8t1f4lBcFfEwuNm/uXYoBAAAAAP////+YeAl8arEGKOcyrWJAYwSboyCstkhHN8zn7/vy7pkYTAEAAAAA/////wHlHgAAAAAAABYAFKdq0umSucBGVkl2MpT6Hgo/0a/xAkcwRAIgMkiJmNFbEi2I3CQYOwyV/JepCnFQRvj4xghkySpFcJMCIGAypkkWltfj+ucvqUIu27tusDAIAAB+rBhX/GV7hPlEASEDyLmWyTLjLfC9kn8pnW42jW5N6EJo5fObjWWEyfLDu9UCSDBFAiEAg9crVtwBPF+sWk+Th6pLwzDjJGItwsUCvoBPtmMTEb4CIDGuM7WOguV0TP21oidF3bSUZlEAjUHWfWzxLKw+3LofASEDfN16xKb70UZSeQyX5Tlh8iRq7np5Nlz9GYdcSU50sKwCSDBFAiEAvotOblaEiBptRWkvb6bj2MGyRjTphKLBLiHYmrRMTCgCIEKJH+z65uPSSz1NIb0d/u3bU9l0xcWk0idEsXjB+BIiASEDrAEiEtrSNKxbh6F/KPaCTafF2LVjCzb75WB+x4xSuoQAAAAAAQEf5R4AAAAAAAAWABSnatLpkrnARlZJdjKU+h4KP9Gv8SIGA3xMuxmPsBAm9aMEUBs3N46DB+Kdts3bZR/Wxt+uM0H4GKtN6bpUAACAAAAAgAAAAIAAAAAAAAAAAAABBBTk7bEOxYcdXDi1eeWraYDufm6eJgEDCOgDAAAAAAAAAAEEFDX3g/pnDXIfsRw8shK42NZn+SdpAQMIiBMAAAAAAAAiAgN8TLsZj7AQJvWjBFAbNzeOgwfinbbN22Uf1sbfrjNB+BirTem6VAAAgAAAAIAAAACAAAAAAAAAAAAA"
// );
// psbt.deserialize(psbtBuf);
// final result = await btc.signPsbt(selectedDevice!, psbt: psbt);
// return result.toHexString();
// },
// method: "Raw Tx",
// func: sendMoney
// ),
// Padding(
// padding: EdgeInsets.only(top: 20),
@ -147,18 +135,18 @@
// ...devices
// .map(
// (device) => Padding(
// padding: EdgeInsets.only(bottom: 20),
// child: DeviceTile(
// onPressed: () {
// setState(() => selectedDevice = device);
// ledger.connect(device);
// },
// title: device.name,
// leading: imageLedger,
// connectionType: device.connectionType,
// ),
// ),
// )
// padding: EdgeInsets.only(bottom: 20),
// child: DeviceTile(
// onPressed: () {
// setState(() => selectedDevice = device);
// ledger.connect(device);
// },
// title: device.name,
// leading: imageLedger,
// connectionType: device.connectionType,
// ),
// ),
// )
// .toList(),
// PrimaryButton(
// text: "Refresh BLE",
@ -188,6 +176,42 @@
// );
// }
//
// Future<String> sendMoney() async {
// final readyInputs = [
// LedgerTransaction(
// rawTx: "010000000001018c055c85c3724c98842d27712771dd0de139711f5940bba2df4615c5522184740000000017160014faf7f6dfb4e70798b92c93f33b4c51024491829df0ffffff022b05c70000000000160014f489f947fd13a1fb44ac168427081d3f30b6ce0cde9dd82e0000000017a914d5eca376cb49d65031220ff9093b7d407073ed0d8702483045022100f648c9f6a9b8f35b6ec29bbfae312c95ed3d56ce6a3f177d994efe90562ec4bd02205b82ce2c94bc0c9d152c3afc668b200bd82f48d6a14e83c66ba0f154cd5f69190121038f1dca119420d4aa7ad04af1c0d65304723789cccc56d335b18692390437f35900000000",
// outputIndex: 0,
// ownerPublicKey:
// HexUtils.decode("03b2e67958ed3356e329e05cf94c3bee6b20c17175ac3b2a1278e073bf44f5d6ec"),
// ownerDerivationPath: "m/84'/2'/0'/0/0",
// sequence: 0xffffffff,
// )
// ];
//
// final outputs = [
// BitcoinOutput(
// address: P2wpkhAddress.fromAddress(
// address: "ltc1qn0g5e36xaj07lqj6w9xn52ng07hud42g3jf5ps",
// network: LitecoinNetwork.mainnet),
// value: BigInt.from(1000000)),
// BitcoinOutput(
// address: P2wpkhAddress.fromAddress(
// address: "ltc1qrx29qz4ghu4j0xk37ptgk7034cwpmjyxhrcnk9",
// network: LitecoinNetwork.mainnet),
// value: BigInt.from(12042705)),
// ];
// return await ltc.createTransaction(selectedDevice!,
// inputs: readyInputs,
// outputs: outputs
// .map((e) => TransactionOutput.fromBigInt(
// e.value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
// .toList(),
// sigHashType: 0x01,
// additionals: ["bech32"],
// isSegWit: true,
// useTrustedInputForSegwit: true);
// }
//
// Widget DebugButton(
// {required String title, required String method, required Future<dynamic> Function() func}) {
// return Padding(

View file

@ -1,6 +1,6 @@
import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
import 'package:flutter/material.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
class DeviceTile extends StatelessWidget {
const DeviceTile({

View file

@ -6,7 +6,6 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
@ -24,8 +23,8 @@ import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
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_bitcoin/bitcoin_receive_page_option.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart';
@ -189,7 +188,7 @@ class CryptoBalanceWidget extends StatelessWidget {
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'assets/images/ledger_nano.png',
'assets/images/hardware_wallet/ledger_nano_x.png',
width: 24,
color: Theme.of(context)
.extension<DashboardPageTheme>()!
@ -382,18 +381,10 @@ class CryptoBalanceWidget extends StatelessWidget {
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).litecoin_mweb,
subTitle: '',
subTitle: S.of(context).litecoin_mweb_description,
hint: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).litecoin_mweb_description,
style: TextStyle(
color: Colors.white,
fontSize: 14,
),
textAlign: TextAlign.center,
),
SizedBox(height: 8),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
@ -401,53 +392,64 @@ class CryptoBalanceWidget extends StatelessWidget {
"https://guides.cakewallet.com/docs/cryptos/litecoin/#mweb"),
mode: LaunchMode.externalApplication,
),
child: Center(
child: Text(
S.of(context).learn_more,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
softWrap: true,
child: Text(
S.of(context).learn_more,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
softWrap: true,
),
),
SizedBox(height: 24),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => _dismissMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
Expanded(
child: ElevatedButton(
onPressed: () => _dismissMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
),
child: Text(
S.of(context).litecoin_mweb_dismiss,
style: TextStyle(color: Colors.white),
),
),
child: Text(
S.of(context).litecoin_mweb_dismiss,
style: TextStyle(color: Colors.white),
),
),
ElevatedButton(
onPressed: () => _enableMweb(context),
child: Text(S.of(context).litecoin_enable_mweb_sync),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: () => _enableMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
child: Text(
S.of(context).enable,
maxLines: 1,
),
),
),
],
),
],
),
onTap: () => {},
icon: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
size: 50,
icon: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Color.fromARGB(255, 11, 70, 129),
size: 40,
),
),
),
),
@ -505,7 +507,7 @@ class CryptoBalanceWidget extends StatelessWidget {
},
));
}
dashboardViewModel.setMwebScanningActive();
dashboardViewModel.setMwebEnabled();
}
Future<void> _dismissMweb(BuildContext context) async {
@ -836,224 +838,293 @@ class BalanceRowWidget extends StatelessWidget {
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
child: Container(
margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
if (currency == CryptoCurrency.ltc)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding: EdgeInsets.only(right: 16, top: 16),
child: Column(
children: [
CakeImageWidget(
imageUrl: 'assets/images/mweb_logo.png',
height: 40,
width: 40,
displayOnError: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: TextStyle(fontSize: 11),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
Container(
margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16),
child: Stack(
children: [
if (currency == CryptoCurrency.ltc)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding: EdgeInsets.only(right: 16, top: 0),
child: Column(
children: [
Container(
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
size: 40,
),
),
),
const SizedBox(height: 10),
const SizedBox(height: 10),
Text(
'MWEB',
style: TextStyle(
fontSize: 15,
fontFamily: 'Lato',
fontWeight: FontWeight.w800,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
),
],
),
),
],
),
if (hasSecondAvailableBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'MWEB',
'${secondAvailableBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w800,
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
secondAvailableBalance,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 6),
if (!isTestnet)
Text(
'${secondAvailableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
height: 1,
),
),
],
),
),
],
),
if (hasSecondAvailableBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${secondAvailableBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
secondAvailableBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
],
),
],
),
),
Container(
margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16),
child: Stack(
children: [
if (hasSecondAdditionalBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${secondAvailableFiatBalance}',
'${secondAdditionalBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.textColor,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
],
),
],
),
],
),
Stack(
children: [
if (hasSecondAdditionalBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${secondAdditionalBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
secondAdditionalBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
'${secondAdditionalFiatBalance}',
textAlign: TextAlign.center,
SizedBox(height: 8),
AutoSizeText(
secondAdditionalBalance,
style: TextStyle(
fontSize: 12,
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.textColor,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
],
),
],
),
// TODO: smarter peg in / out buttons
// if (currency == CryptoCurrency.ltc)
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// Container(
// margin: EdgeInsets.only(top: 24, right: 8),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Theme.of(context).highlightColor,
// ),
// onPressed: () {
// final mwebAddress =
// bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet);
// if (mwebAddress == null) return;
// final paymentRequest =
// PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}"));
// Navigator.of(context)
// .pushNamed(Routes.send, arguments: paymentRequest);
// },
// child: Container(
// color: Colors.transparent,
// margin: EdgeInsets.all(4),
// child: Column(
// mainAxisSize: MainAxisSize.max,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Container(
// alignment: Alignment.center,
// decoration: BoxDecoration(shape: BoxShape.circle),
// child: Image.asset(
// 'assets/images/received.png',
// color: Theme.of(context)
// .extension<BalancePageTheme>()!
// .balanceAmountColor,
// width: 64,
// height: 32,
// ),
// ),
// SizedBox(height: 4),
// Text(
// S.of(context).litecoin_mweb_pegin,
// style: TextStyle(
// fontSize: 10,
// color: Theme.of(context)
// .extension<DashboardPageTheme>()!
// .cardTextColor),
// )
// ],
// ),
// ),
// ),
// ),
// ],
// ),
],
SizedBox(height: 4),
if (!isTestnet)
Text(
'${secondAdditionalFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
height: 1,
),
),
],
),
],
),
],
),
),
IntrinsicHeight(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegin,
child: OutlinedButton(
onPressed: () {
final mwebAddress =
bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((mwebAddress?.isNotEmpty ?? false)) {
paymentRequest =
PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.nonMweb,
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400
.withAlpha(50),
side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/received.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegin,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
SizedBox(width: 24),
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegout,
child: OutlinedButton(
onPressed: () {
final litecoinAddress =
bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((litecoinAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${litecoinAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.mweb,
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400
.withAlpha(50),
side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/upload.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegout,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
],
),
),
),
SizedBox(height: 16),
],
),
),

View file

@ -97,14 +97,13 @@ class MenuWidgetState extends State<MenuWidget> {
@override
Widget build(BuildContext context) {
List<SettingActions> items = SettingActions.all;
List<SettingActions> items = List.of(SettingActions.all);
if (!widget.dashboardViewModel.hasSilentPayments) {
items.removeWhere((element) => element.name(context) == S.of(context).silent_payments_settings);
}
// if (!widget.dashboardViewModel.hasMweb) {
// itemCount--;
// items.removeWhere((element) => element.name(context) == S.of(context).litecoin_mweb_settings);
// }
if (!widget.dashboardViewModel.hasMweb) {
items.removeWhere((element) => element.name(context) == S.of(context).litecoin_mweb_settings);
}
int itemCount = items.length;
moneroIcon = Image.asset('assets/images/monero_menu.png',
@ -191,11 +190,6 @@ class MenuWidgetState extends State<MenuWidget> {
final item = items[index];
if (!widget.dashboardViewModel.hasMweb &&
item.name(context) == S.of(context).litecoin_mweb_settings) {
return const SizedBox();
}
final isLastTile = index == itemCount - 1;
return SettingActionButton(

View file

@ -67,7 +67,7 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
final mainImageColor = Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor;
final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor;
final imageColor = widget.themeType == ThemeType.bright ? brightImageColor : mainImageColor;
final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor);
final imageLedger = Image.asset('assets/images/hardware_wallet/ledger_nano_x.png', width: 40, color: imageColor);
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
@ -186,4 +186,4 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
}
}
}
}
}

View file

@ -135,10 +135,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
setState(() => _setInactive(true));
}
if (widget.appStore.wallet?.type == WalletType.litecoin) {
widget.appStore.wallet?.stopSync();
}
break;
case AppLifecycleState.resumed:
widget.authService.requireAuth().then((value) {
@ -148,9 +144,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
});
}
});
if (widget.appStore.wallet?.type == WalletType.litecoin) {
widget.appStore.wallet?.startSync();
}
break;
default:
break;

View file

@ -28,6 +28,7 @@ import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
@ -394,16 +395,19 @@ class SendPage extends BasePage {
if (sendViewModel.wallet.isHardwareWallet) {
if (!sendViewModel.ledgerViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
await Navigator.of(context).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
onConnectDevice: (BuildContext context, _) {
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
sendViewModel.ledgerViewModel!
.setLedger(sendViewModel.wallet);
Navigator.of(context).pop();
},
));
} else {
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
sendViewModel.ledgerViewModel!
.setLedger(sendViewModel.wallet);
}
}
@ -509,6 +513,10 @@ class SendPage extends BasePage {
newContactAddress =
newContactAddress ?? sendViewModel.newContactAddress();
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) {
newContactAddress = null;
}
final successMessage = S.of(_dialogContext).send_success(
sendViewModel.selectedCryptoCurrency.toString());

View file

@ -14,7 +14,6 @@ import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
@ -373,7 +372,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
padding: EdgeInsets.only(top: 6),
child: GestureDetector(
key: ValueKey('send_page_unspent_coin_button_key'),
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
onTap: () => Navigator.of(context).pushNamed(
Routes.unspentCoinsList,
arguments: widget.sendViewModel.coinTypeToSpendFrom,
),
child: Container(
color: Colors.transparent,
child: Row(

View file

@ -4,7 +4,6 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -33,9 +32,9 @@ class MwebSettingsPage extends BasePage {
),
SettingsSwitcherCell(
title: S.current.litecoin_mweb_always_scan,
value: _mwebSettingsViewModel.mwebAlwaysScan,
value: _mwebSettingsViewModel.mwebEnabled,
onValueChange: (_, bool value) {
_mwebSettingsViewModel.setMwebAlwaysScan(value);
_mwebSettingsViewModel.setMwebEnabled(value);
},
),
SettingsCellWithArrow(

View file

@ -18,7 +18,6 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class WalletEditPage extends BasePage {
WalletEditPage({
required this.pageArguments,
@ -86,8 +85,9 @@ class WalletEditPage extends BasePage {
child: LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState?.validate() ?? false) {
if (pageArguments.walletNewVM!
.nameExists(walletEditViewModel.newName)) {
if (!pageArguments.isWalletGroup &&
pageArguments.walletNewVM!
.nameExists(walletEditViewModel.newName)) {
showPopUp<void>(
context: context,
builder: (_) {

View file

@ -31,7 +31,6 @@ class SettingActions {
walletSettingAction,
addressBookSettingAction,
silentPaymentsSettingAction,
litecoinMwebSettingAction,
securityBackupSettingAction,
privacySettingAction,
displaySettingAction,
@ -50,7 +49,7 @@ class SettingActions {
static SettingActions litecoinMwebSettingAction = SettingActions._(
name: (context) => S.of(context).litecoin_mweb_settings,
image: 'assets/images/bitcoin_menu.png',
image: 'assets/images/litecoin_menu.png',
onTap: (BuildContext context) {
Navigator.pop(context);
Navigator.of(context).pushNamed(Routes.mwebSettings);

View file

@ -37,7 +37,8 @@ abstract class AppStoreBase with Store {
@action
Future<void> changeCurrentWallet(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet) async {
this.wallet?.close();
bool changingToSameWalletType = this.wallet?.type == wallet.type;
this.wallet?.close(shouldCleanup: !changingToSameWalletType);
this.wallet = wallet;
this.wallet!.setExceptionHandler(ExceptionHandler.onError);

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
@ -31,6 +32,7 @@ class BalanceRecord {
required this.fiatSecondAdditionalBalance,
required this.asset,
required this.formattedAssetTitle});
final String fiatAdditionalBalance;
final String fiatAvailableBalance;
final String fiatFrozenBalance;
@ -53,7 +55,22 @@ abstract class BalanceViewModelBase with Store {
: isReversing = false,
isShowCard = appStore.wallet!.walletInfo.isShowIntroCakePayCard,
wallet = appStore.wallet! {
reaction((_) => appStore.wallet, _onWalletChange);
reaction((_) => appStore.wallet, (wallet) {
_onWalletChange(wallet);
_checkMweb();
});
_checkMweb();
reaction((_) => settingsStore.mwebAlwaysScan, (bool value) {
_checkMweb();
});
}
void _checkMweb() {
if (wallet.type == WalletType.litecoin) {
mwebEnabled = bitcoin!.getMwebEnabled(wallet);
}
}
final AppStore appStore;
@ -336,14 +353,19 @@ abstract class BalanceViewModelBase with Store {
});
}
@observable
bool mwebEnabled = false;
@computed
bool get hasAdditionalBalance => _hasAdditionalBalanceForWalletType(wallet.type);
@computed
bool get hasSecondAdditionalBalance => _hasSecondAdditionalBalanceForWalletType(wallet.type);
bool get hasSecondAdditionalBalance =>
mwebEnabled && _hasSecondAdditionalBalanceForWalletType(wallet.type);
@computed
bool get hasSecondAvailableBalance => _hasSecondAvailableBalanceForWalletType(wallet.type);
bool get hasSecondAvailableBalance =>
mwebEnabled && _hasSecondAvailableBalanceForWalletType(wallet.type);
bool _hasAdditionalBalanceForWalletType(WalletType type) {
switch (type) {
@ -358,15 +380,16 @@ abstract class BalanceViewModelBase with Store {
}
bool _hasSecondAdditionalBalanceForWalletType(WalletType type) {
if (wallet.type == WalletType.litecoin && settingsStore.mwebAlwaysScan) {
// if ((wallet.balance[CryptoCurrency.ltc]?.secondAdditional ?? 0) > 0)
return true;
if (wallet.type == WalletType.litecoin) {
if ((wallet.balance[CryptoCurrency.ltc]?.secondAdditional ?? 0) > 0) {
return true;
}
}
return false;
}
bool _hasSecondAvailableBalanceForWalletType(WalletType type) {
if (wallet.type == WalletType.litecoin && settingsStore.mwebAlwaysScan) {
if (wallet.type == WalletType.litecoin) {
return true;
}
return false;

View file

@ -1,4 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io' show Platform;
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
@ -222,7 +224,10 @@ abstract class DashboardViewModelBase with Store {
// subname = nano!.getCurrentAccount(_wallet).label;
// }
reaction((_) => appStore.wallet, _onWalletChange);
reaction((_) => appStore.wallet, (wallet) {
_onWalletChange(wallet);
_checkMweb();
});
connectMapToListWithTransform(
appStore.wallet!.transactionHistory.transactions,
@ -256,16 +261,16 @@ abstract class DashboardViewModelBase with Store {
});
}
_checkMweb();
reaction((_) => settingsStore.mwebAlwaysScan, (bool value) {
_checkMweb();
});
}
void _checkMweb() {
if (hasMweb) {
mwebScanningActive = bitcoin!.getMwebEnabled(wallet);
settingsStore.mwebEnabled = mwebScanningActive;
reaction((_) => settingsStore.mwebAlwaysScan, (bool alwaysScan) {
if (alwaysScan) {
mwebScanningActive = true;
} else {
mwebScanningActive = false;
}
});
mwebEnabled = bitcoin!.getMwebEnabled(wallet);
balanceViewModel.mwebEnabled = mwebEnabled;
}
}
@ -430,34 +435,36 @@ abstract class DashboardViewModelBase with Store {
}
@computed
bool get hasMweb => wallet.type == WalletType.litecoin;
bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid) && !wallet.isHardwareWallet;
@computed
bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebScanningActive;
bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebEnabled;
@observable
bool mwebScanningActive = false;
bool mwebEnabled = false;
@computed
bool get hasEnabledMwebBefore => settingsStore.hasEnabledMwebBefore;
@action
void setMwebScanningActive() {
void setMwebEnabled() {
if (!hasMweb) {
return;
}
settingsStore.hasEnabledMwebBefore = true;
mwebScanningActive = true;
mwebEnabled = true;
bitcoin!.setMwebEnabled(wallet, true);
balanceViewModel.mwebEnabled = true;
settingsStore.mwebAlwaysScan = true;
}
@action
void dismissMweb() {
settingsStore.mwebCardDisplay = false;
balanceViewModel.mwebEnabled = false;
settingsStore.mwebAlwaysScan = false;
mwebScanningActive = false;
mwebEnabled = false;
bitcoin!.setMwebEnabled(wallet, false);
}

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
@ -9,11 +10,19 @@ import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
import 'package:mobx/mobx.dart';
import 'package:permission_handler/permission_handler.dart';
class LedgerViewModel {
late final Ledger ledger;
part 'ledger_view_model.g.dart';
class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
abstract class LedgerViewModelBase with Store {
// late final Ledger ledger;
late final sdk.LedgerInterface ledgerPlusBLE;
late final sdk.LedgerInterface ledgerPlusUSB;
bool get _doesSupportHardwareWallets {
if (!DeviceInfo.instance.isMobile) {
@ -21,53 +30,97 @@ class LedgerViewModel {
}
if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
return DeviceConnectionType.supportedConnectionTypes(
WalletType.monero, Platform.isIOS)
.isNotEmpty;
}
return true;
}
LedgerViewModel() {
LedgerViewModelBase() {
if (_doesSupportHardwareWallets) {
ledger = Ledger(
options: LedgerOptions(
scanMode: ScanMode.balanced,
maxScanDuration: const Duration(minutes: 5),
),
onPermissionRequest: (_) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.bluetoothAdvertise,
].request();
reaction((_) => bleIsEnabled, (_) {
if (bleIsEnabled) _initBLE();
});
updateBleState();
return statuses.values.where((status) => status.isDenied).isEmpty;
},
);
if (!Platform.isIOS) {
ledgerPlusUSB = sdk.LedgerInterface.usb();
}
}
}
Future<void> connectLedger(LedgerDevice device) async {
await ledger.connect(device);
@observable
bool bleIsEnabled = false;
if (device.connectionType == ConnectionType.usb) _device = device;
bool _bleIsInitialized = false;
Future<void> _initBLE() async {
if (bleIsEnabled && !_bleIsInitialized) {
ledgerPlusBLE = sdk.LedgerInterface.ble(onPermissionRequest: (_) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.bluetoothAdvertise,
].request();
return statuses.values.where((status) => status.isDenied).isEmpty;
});
_bleIsInitialized = true;
}
}
LedgerDevice? _device;
Future<void> updateBleState() async {
final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
bool get isConnected => ledger.devices.isNotEmpty || _device != null;
final newState = bleState == sdk.AvailabilityState.poweredOn;
LedgerDevice get device => _device ?? ledger.devices.first;
if (newState != bleIsEnabled) bleIsEnabled = newState;
}
Stream<sdk.LedgerDevice> scanForBleDevices() => ledgerPlusBLE.scan();
Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async {
if (isConnected) {
try {
await _connection!.disconnect();
} catch (_) {}
}
final ledger = device.connectionType == sdk.ConnectionType.ble
? ledgerPlusBLE
: ledgerPlusUSB;
if (_connectionChangeListener == null) {
_connectionChangeListener = ledger.deviceStateChanges.listen((event) {
print('Ledger Device State Changed: $event');
if (event == sdk.BleConnectionState.disconnected) {
_connection = null;
_connectionChangeListener?.cancel();
}
});
}
_connection = await ledger.connect(device);
}
StreamSubscription<sdk.BleConnectionState>? _connectionChangeListener;
sdk.LedgerConnection? _connection;
bool get isConnected => _connection != null && !(_connection!.isDisconnected);
sdk.LedgerConnection get connection => _connection!;
void setLedger(WalletBase wallet) {
switch (wallet.type) {
case WalletType.bitcoin:
return bitcoin!.setLedger(wallet, ledger, device);
case WalletType.litecoin:
return bitcoin!.setLedgerConnection(wallet, connection);
case WalletType.ethereum:
return ethereum!.setLedger(wallet, ledger, device);
return ethereum!.setLedgerConnection(wallet, connection);
case WalletType.polygon:
return polygon!.setLedger(wallet, ledger, device);
return polygon!.setLedgerConnection(wallet, connection);
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}

View file

@ -65,15 +65,16 @@ class LinkViewModel {
if (isNanoGptLink) {
switch (currentLink?.authority ?? '') {
case "exchange":
case "send":
return PaymentRequest.fromUri(currentLink);
case "send":
return {"paymentRequest": PaymentRequest.fromUri(currentLink)};
case "buy":
return true;
}
}
if (_isValidPaymentUri) {
return PaymentRequest.fromUri(currentLink);
return {"paymentRequest": PaymentRequest.fromUri(currentLink)};
}
return null;

View file

@ -37,7 +37,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
spendKey = '',
wif = '',
address = '',
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, type: type, isRecovery: true);
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
type: type, isRecovery: true);
@observable
int height;
@ -112,7 +113,14 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
);
case WalletType.bitcoin:
case WalletType.litecoin:
final derivationInfo = (await getDerivationInfoFromQRCredentials(restoreWallet)).first;
final derivationInfoList = await getDerivationInfoFromQRCredentials(restoreWallet);
DerivationInfo derivationInfo;
if (derivationInfoList.isEmpty) {
derivationInfo = getDefaultCreateDerivation()!;
} else {
derivationInfo = derivationInfoList.first;
}
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '',

View file

@ -20,10 +20,10 @@ import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/exceptions.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/core/address_validator.dart';
@ -67,8 +67,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
this.balanceViewModel,
this.contactListViewModel,
this.transactionDescriptionBox,
this.ledgerViewModel,
) : state = InitialExecutionState(),
this.ledgerViewModel, {
this.coinTypeToSpendFrom = UnspentCoinType.any,
}) : state = InitialExecutionState(),
currencies = appStore.wallet!.balance.keys.toList(),
selectedCryptoCurrency = appStore.wallet!.currency,
hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) ||
@ -97,6 +98,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
ObservableList<Output> outputs;
final UnspentCoinType coinTypeToSpendFrom;
@action
void addOutput() {
outputs
@ -119,7 +122,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@computed
bool get isBatchSending => outputs.length > 1;
bool get shouldDisplaySendALL => walletType != WalletType.solana;
bool get shouldDisplaySendALL {
if (walletType == WalletType.solana) return false;
if (walletType == WalletType.ethereum && selectedCryptoCurrency == CryptoCurrency.eth)
return false;
if (walletType == WalletType.polygon && selectedCryptoCurrency == CryptoCurrency.matic)
return false;
return true;
}
@computed
String get pendingTransactionFiatAmount {
@ -217,7 +230,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
PendingTransaction? pendingTransaction;
@computed
String get balance => wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
String get balance {
if (coinTypeToSpendFrom == UnspentCoinType.mweb) {
return balanceViewModel.balances.values.first.secondAvailableBalance;
} else if (coinTypeToSpendFrom == UnspentCoinType.nonMweb) {
return balanceViewModel.balances.values.first.availableBalance;
}
return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
}
@computed
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
@ -387,16 +407,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = ExecutedSuccessfullyState();
return pendingTransaction;
} catch (e) {
if (e is LedgerException) {
final errorCode = e.errorCode.toRadixString(16);
final fallbackMsg =
e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode";
final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg;
state = FailureState(errorMsg);
} else {
// if (e is LedgerException) {
// final errorCode = e.errorCode.toRadixString(16);
// final fallbackMsg =
// e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode";
// final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg;
//
// state = FailureState(errorMsg);
// } else {
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
}
// }
}
return null;
}
@ -461,12 +481,18 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
nano!.updateTransactions(wallet);
}
if (pendingTransaction!.id.isNotEmpty) {
final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}';
_settingsStore.shouldSaveRecipientAddress
? await transactionDescriptionBox.add(TransactionDescription(
id: pendingTransaction!.id, recipientAddress: address, transactionNote: note))
: await transactionDescriptionBox
.add(TransactionDescription(id: pendingTransaction!.id, transactionNote: note));
id: descriptionKey,
recipientAddress: address,
transactionNote: note))
: await transactionDescriptionBox.add(TransactionDescription(
id: descriptionKey,
transactionNote: note));
}
state = TransactionCommitted();
@ -494,8 +520,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
return bitcoin!.createBitcoinTransactionCredentials(outputs,
priority: priority!, feeRate: customBitcoinFeeRate);
return bitcoin!.createBitcoinTransactionCredentials(
outputs,
priority: priority!,
feeRate: customBitcoinFeeRate,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
case WalletType.monero:
return monero!

View file

@ -8,7 +8,10 @@ part 'mweb_settings_view_model.g.dart';
class MwebSettingsViewModel = MwebSettingsViewModelBase with _$MwebSettingsViewModel;
abstract class MwebSettingsViewModelBase with Store {
MwebSettingsViewModelBase(this._settingsStore, this._wallet);
MwebSettingsViewModelBase(this._settingsStore, this._wallet) {
mwebEnabled = bitcoin!.getMwebEnabled(_wallet);
_settingsStore.mwebAlwaysScan = mwebEnabled;
}
final SettingsStore _settingsStore;
final WalletBase _wallet;
@ -16,8 +19,8 @@ abstract class MwebSettingsViewModelBase with Store {
@computed
bool get mwebCardDisplay => _settingsStore.mwebCardDisplay;
@computed
bool get mwebAlwaysScan => _settingsStore.mwebAlwaysScan;
@observable
late bool mwebEnabled;
@action
void setMwebCardDisplay(bool value) {
@ -25,8 +28,9 @@ abstract class MwebSettingsViewModelBase with Store {
}
@action
void setMwebAlwaysScan(bool value) {
_settingsStore.mwebAlwaysScan = value;
void setMwebEnabled(bool value) {
mwebEnabled = value;
bitcoin!.setMwebEnabled(_wallet, value);
_settingsStore.mwebAlwaysScan = value;
}
}

View file

@ -110,9 +110,11 @@ abstract class TransactionDetailsViewModelBase with Store {
} catch (e) {}
}));
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
final description = transactionDescriptionBox.values.firstWhere(
(val) => val.id == transactionInfo.txHash,
orElse: () => TransactionDescription(id: transactionInfo.txHash));
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
orElse: () => TransactionDescription(id: descriptionKey));
items.add(TextFieldListItem(
title: S.current.note_tap_to_change,

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/unspent_transaction_output.dart';
import 'package:cw_core/wallet_base.dart';
@ -16,9 +17,11 @@ part 'unspent_coins_list_view_model.g.dart';
class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel;
abstract class UnspentCoinsListViewModelBase with Store {
UnspentCoinsListViewModelBase(
{required this.wallet, required Box<UnspentCoinsInfo> unspentCoinsInfo})
: _unspentCoinsInfo = unspentCoinsInfo,
UnspentCoinsListViewModelBase({
required this.wallet,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
this.coinTypeToSpendFrom = UnspentCoinType.any,
}) : _unspentCoinsInfo = unspentCoinsInfo,
_items = ObservableList<UnspentCoinsItem>() {
_updateUnspentCoinsInfo();
_updateUnspents();
@ -26,6 +29,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
WalletBase wallet;
final Box<UnspentCoinsInfo> _unspentCoinsInfo;
final UnspentCoinType coinTypeToSpendFrom;
@observable
ObservableList<UnspentCoinsItem> _items;
@ -103,7 +107,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
return bitcoin!.getUnspents(wallet);
return bitcoin!.getUnspents(wallet, coinTypeToSpendFrom: coinTypeToSpendFrom);
default:
return List.empty();
}

View file

@ -1,14 +1,15 @@
import 'dart:math';
import 'dart:developer' as dev;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart';
@ -24,16 +25,14 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
part 'wallet_address_list_view_model.g.dart';
class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel;
class WalletAddressListViewModel = WalletAddressListViewModelBase
with _$WalletAddressListViewModel;
abstract class PaymentURI {
PaymentURI({required this.amount, required this.address});
@ -43,12 +42,11 @@ abstract class PaymentURI {
}
class MoneroURI extends PaymentURI {
MoneroURI({required String amount, required String address})
: super(amount: amount, address: address);
MoneroURI({required super.amount, required super.address});
@override
String toString() {
var base = 'monero:' + address;
var base = 'monero:$address';
if (amount.isNotEmpty) {
base += '?tx_amount=${amount.replaceAll(',', '.')}';
@ -59,12 +57,11 @@ class MoneroURI extends PaymentURI {
}
class HavenURI extends PaymentURI {
HavenURI({required String amount, required String address})
: super(amount: amount, address: address);
HavenURI({required super.amount, required super.address});
@override
String toString() {
var base = 'haven:' + address;
var base = 'haven:$address';
if (amount.isNotEmpty) {
base += '?tx_amount=${amount.replaceAll(',', '.')}';
@ -75,12 +72,11 @@ class HavenURI extends PaymentURI {
}
class BitcoinURI extends PaymentURI {
BitcoinURI({required String amount, required String address})
: super(amount: amount, address: address);
BitcoinURI({required super.amount, required super.address});
@override
String toString() {
var base = 'bitcoin:' + address;
var base = 'bitcoin:$address';
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
@ -91,12 +87,11 @@ class BitcoinURI extends PaymentURI {
}
class LitecoinURI extends PaymentURI {
LitecoinURI({required String amount, required String address})
: super(amount: amount, address: address);
LitecoinURI({required super.amount, required super.address});
@override
String toString() {
var base = 'litecoin:' + address;
var base = 'litecoin:$address';
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
@ -107,12 +102,11 @@ class LitecoinURI extends PaymentURI {
}
class EthereumURI extends PaymentURI {
EthereumURI({required String amount, required String address})
: super(amount: amount, address: address);
EthereumURI({required super.amount, required super.address});
@override
String toString() {
var base = 'ethereum:' + address;
var base = 'ethereum:$address';
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
@ -123,8 +117,7 @@ class EthereumURI extends PaymentURI {
}
class BitcoinCashURI extends PaymentURI {
BitcoinCashURI({required String amount, required String address})
: super(amount: amount, address: address);
BitcoinCashURI({required super.amount, required super.address});
@override
String toString() {
@ -139,12 +132,11 @@ class BitcoinCashURI extends PaymentURI {
}
class NanoURI extends PaymentURI {
NanoURI({required String amount, required String address})
: super(amount: amount, address: address);
NanoURI({required super.amount, required super.address});
@override
String toString() {
var base = 'nano:' + address;
var base = 'nano:$address';
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
@ -154,12 +146,11 @@ class NanoURI extends PaymentURI {
}
class PolygonURI extends PaymentURI {
PolygonURI({required String amount, required String address})
: super(amount: amount, address: address);
PolygonURI({required super.amount, required super.address});
@override
String toString() {
var base = 'polygon:' + address;
var base = 'polygon:$address';
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
@ -170,12 +161,12 @@ class PolygonURI extends PaymentURI {
}
class SolanaURI extends PaymentURI {
SolanaURI({required String amount, required String address})
: super(amount: amount, address: address);
SolanaURI({required super.amount, required super.address});
@override
String toString() {
var base = 'solana:' + address;
var base = 'solana:$address';
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
@ -185,12 +176,12 @@ class SolanaURI extends PaymentURI {
}
class TronURI extends PaymentURI {
TronURI({required String amount, required String address})
: super(amount: amount, address: address);
TronURI({required super.amount, required super.address});
@override
String toString() {
var base = 'tron:' + address;
var base = 'tron:$address';
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
@ -200,12 +191,11 @@ class TronURI extends PaymentURI {
}
class WowneroURI extends PaymentURI {
WowneroURI({required String amount, required String address})
: super(amount: amount, address: address);
WowneroURI({required super.amount, required super.address});
@override
String toString() {
var base = 'wownero:' + address;
var base = 'wownero:$address';
if (amount.isNotEmpty) {
base += '?tx_amount=${amount.replaceAll(',', '.')}';
@ -215,7 +205,8 @@ class WowneroURI extends PaymentURI {
}
}
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
abstract class WalletAddressListViewModelBase
extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({
required AppStore appStore,
required this.yatStore,
@ -223,9 +214,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}) : _baseItems = <ListItem>[],
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
hasAccounts = appStore.wallet!.type == WalletType.monero ||
appStore.wallet!.type == WalletType.wownero ||
appStore.wallet!.type == WalletType.haven,
hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
.contains(appStore.wallet!.type),
amount = '',
_settingsStore = appStore.settingsStore,
super(appStore: appStore) {
@ -237,9 +227,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
_init();
selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
hasAccounts = wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero ||
wallet.type == WalletType.haven;
hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
.contains(wallet.type);
}
static const String _cryptoNumberPattern = '0.00000000';
@ -249,7 +238,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
final FiatConversionStore fiatConversionStore;
final SettingsStore _settingsStore;
List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
double? _fiatRate;
String _rawAmount = '';
List<Currency> get currencies =>
[walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
String get buttonTitle {
if (isElectrumWallet) {
@ -275,9 +268,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType get type => wallet.type;
@computed
WalletAddressListItem get address {
return WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
}
WalletAddressListItem get address => WalletAddressListItem(
address: wallet.walletAddresses.address, isPrimary: false);
@computed
PaymentURI get uri {
@ -321,8 +313,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
final addressList = ObservableList<ListItem>();
if (wallet.type == WalletType.monero) {
final primaryAddress = monero!.getSubaddressList(wallet).subaddresses.first;
final addressItems = monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final primaryAddress =
monero!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem(
@ -338,8 +332,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
if (wallet.type == WalletType.wownero) {
final primaryAddress = wownero!.getSubaddressList(wallet).subaddresses.first;
final addressItems = wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final primaryAddress =
wownero!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem(
@ -352,8 +348,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
if (wallet.type == WalletType.haven) {
final primaryAddress = haven!.getSubaddressList(wallet).subaddresses.first;
final addressItems = haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final primaryAddress =
haven!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem(
@ -367,7 +365,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (isElectrumWallet) {
if (bitcoin!.hasSelectedSilentPayments(wallet)) {
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final addressItems =
bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final isPrimary = address.id == 0;
return WalletAddressListItem(
@ -418,7 +417,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) {
// find the index of the last item with a txCount > 0
final addressItemsList = addressItems.toList();
int index = addressItemsList.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
int index = addressItemsList
.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
if (index == -1) {
index = 0;
}
@ -432,19 +432,22 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (wallet.type == WalletType.ethereum) {
final primaryAddress = ethereum!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.polygon) {
final primaryAddress = polygon!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.solana) {
final primaryAddress = solana!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.nano) {
@ -458,18 +461,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (wallet.type == WalletType.tron) {
final primaryAddress = tron!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
}
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
(addressList[i] as WalletAddressListItem).isHidden = wallet
.walletAddresses.hiddenAddresses
.contains((addressList[i] as WalletAddressListItem).address);
}
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses
(addressList[i] as WalletAddressListItem).isManual = wallet
.walletAddresses.manualAddresses
.contains((addressList[i] as WalletAddressListItem).address);
}
@ -487,7 +493,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
Future<void> toggleHideAddress(WalletAddressListItem item) async {
if (item.isHidden) {
wallet.walletAddresses.hiddenAddresses.removeWhere((element) => element == item.address);
wallet.walletAddresses.hiddenAddresses
.removeWhere((element) => element == item.address);
} else {
wallet.walletAddresses.hiddenAddresses.add(item.address);
}
@ -512,57 +519,58 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@computed
String get accountLabel {
if (wallet.type == WalletType.monero) {
return monero!.getCurrentAccount(wallet).label;
switch (wallet.type) {
case WalletType.monero:
return monero!.getCurrentAccount(wallet).label;
case WalletType.wownero:
wownero!.getCurrentAccount(wallet).label;
case WalletType.haven:
return haven!.getCurrentAccount(wallet).label;
default:
return '';
}
if (wallet.type == WalletType.wownero) {
return wownero!.getCurrentAccount(wallet).label;
}
if (wallet.type == WalletType.haven) {
return haven!.getCurrentAccount(wallet).label;
}
return '';
}
@computed
bool get hasAddressList =>
wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero ||
wallet.type == WalletType.haven ||
wallet.type == WalletType.bitcoinCash ||
wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin;
bool get hasAddressList => [
WalletType.monero,
WalletType.wownero,
WalletType.haven,
WalletType.bitcoinCash,
WalletType.bitcoin,
WalletType.litecoin
].contains(wallet.type);
@computed
bool get isElectrumWallet =>
wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash;
bool get isElectrumWallet => [
WalletType.bitcoin,
WalletType.litecoin,
WalletType.bitcoinCash
].contains(wallet.type);
@computed
bool get isBalanceAvailable => isElectrumWallet;
@computed
bool get isReceivedAvailable =>
wallet.type == WalletType.monero || wallet.type == WalletType.wownero;
[WalletType.monero, WalletType.wownero].contains(wallet.type);
@computed
bool get isSilentPayments =>
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
wallet.type == WalletType.bitcoin &&
bitcoin!.hasSelectedSilentPayments(wallet);
@computed
bool get isAutoGenerateSubaddressEnabled =>
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
_settingsStore.autoGenerateSubaddressStatus !=
AutoGenerateSubaddressStatus.disabled &&
!isSilentPayments;
@computed
bool get showAddManualAddresses =>
!isAutoGenerateSubaddressEnabled ||
wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero;
[WalletType.monero, WalletType.wownero].contains(wallet.type);
List<ListItem> _baseItems;
@ -574,7 +582,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@action
Future<void> setAddressType(dynamic option) async {
if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) {
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) {
await bitcoin!.setAddressType(wallet, option);
}
}
@ -586,13 +594,15 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
_baseItems.add(WalletAddressHiddenListHeader());
}
if (wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero ||
wallet.type == WalletType.haven) {
if ([
WalletType.monero,
WalletType.wownero,
WalletType.haven,
].contains(wallet.type)) {
_baseItems.add(WalletAccountListHeader());
}
if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) {
if (![WalletType.nano, WalletType.banano].contains(wallet.type)) {
_baseItems.add(WalletAddressListHeader());
}
if (wallet.isEnabledAutoGenerateSubaddress) {
@ -603,11 +613,27 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@action
void selectCurrency(Currency currency) {
selectedCurrency = currency;
if (currency is FiatCurrency && _settingsStore.fiatCurrency != currency) {
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
dev.log("Requesting Fiat rate for $cryptoCurrency-$currency");
FiatConversionService.fetchPrice(
crypto: cryptoCurrency,
fiat: currency,
torOnly: _settingsStore.fiatApiMode == FiatApiMode.torOnly,
).then((value) {
dev.log("Received Fiat rate 1 $cryptoCurrency = $value $currency");
_fiatRate = value;
_convertAmountToCrypto();
});
}
}
@action
void changeAmount(String amount) {
this.amount = amount;
this._rawAmount = amount;
if (selectedCurrency is FiatCurrency) {
_convertAmountToCrypto();
}
@ -618,11 +644,20 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
searchText = text;
}
@action
void _convertAmountToCrypto() {
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
final fiatRate =
_fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
if (fiatRate <= 0.0) {
dev.log("Invalid Fiat Rate $fiatRate");
amount = '';
return;
}
try {
final crypto =
double.parse(amount.replaceAll(',', '.')) / fiatConversionStore.prices[cryptoCurrency]!;
final crypto = double.parse(_rawAmount.replaceAll(',', '.')) / fiatRate;
final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
if (amount != cryptoAmountTmp) {
amount = cryptoAmountTmp;

View file

@ -115,7 +115,9 @@ abstract class WalletCreationVMBase with Store {
getIt.get<BackgroundTasks>().registerSyncTask();
_appStore.authenticationStore.allowed();
state = ExecutedSuccessfullyState();
} catch (e, _) {
} catch (e, s) {
print("error: $e");
print("stack: $s");
state = FailureState(e.toString());
}
}
@ -188,36 +190,43 @@ abstract class WalletCreationVMBase with Store {
}
}
Future<List<DerivationInfo>> getDerivationInfoFromQRCredentials(RestoredWallet restoreWallet) async {
var list = <DerivationInfo>[];
final walletType = restoreWallet.type;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
Future<List<DerivationInfo>> getDerivationInfoFromQRCredentials(
RestoredWallet restoreWallet) async {
var list = <DerivationInfo>[];
final walletType = restoreWallet.type;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
passphrase: restoreWallet.passphrase,
);
case WalletType.nano:
return nanoUtil!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
);
default:
break;
}
return list;
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
final derivationList = await bitcoin!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
passphrase: restoreWallet.passphrase,
);
if (derivationList.firstOrNull?.transactionsCount == 0 && derivationList.length > 1)
return [];
return derivationList;
case WalletType.nano:
return nanoUtil!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
);
default:
break;
}
return list;
}
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
Future<WalletBase> process(WalletCredentials credentials) => throw UnimplementedError();
Future<WalletCredentials> getWalletCredentialsFromQRCredentials(RestoredWallet restoreWallet) async =>
Future<WalletCredentials> getWalletCredentialsFromQRCredentials(
RestoredWallet restoreWallet) async =>
throw UnimplementedError();
Future<WalletBase> processFromRestoredWallet(

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