mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into secure_storage_fresh_install
This commit is contained in:
commit
e56156c972
145 changed files with 2510 additions and 1352 deletions
1
.github/workflows/pr_test_build_android.yml
vendored
1
.github/workflows/pr_test_build_android.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
useSSL: true
|
||||
-
|
||||
uri: btc-electrum.cakewallet.com:50002
|
||||
useSSL: true
|
||||
isDefault: true
|
||||
-
|
||||
uri: electrs.cakewallet.com:50001
|
||||
|
|
BIN
assets/images/hardware_wallet/ledger_flex.png
Normal file
BIN
assets/images/hardware_wallet/ledger_flex.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/hardware_wallet/ledger_nano_s.png
Normal file
BIN
assets/images/hardware_wallet/ledger_nano_s.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/images/hardware_wallet/ledger_nano_x.png
Normal file
BIN
assets/images/hardware_wallet/ledger_nano_x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/images/hardware_wallet/ledger_stax.png
Normal file
BIN
assets/images/hardware_wallet/ledger_stax.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1,2 +1,3 @@
|
|||
Enhance auto-address generation for Monero
|
||||
Bug fixes and enhancements
|
||||
Monero enhancements
|
||||
Introducing StealthEx and LetxExchange
|
||||
Bug fixes
|
|
@ -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
|
|
@ -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`
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -30,7 +30,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
|
||||
static const allLitecoin = [
|
||||
BitcoinReceivePageOption.p2wpkh,
|
||||
BitcoinReceivePageOption.mweb
|
||||
BitcoinReceivePageOption.mweb,
|
||||
];
|
||||
|
||||
BitcoinAddressType toType() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
46
cw_bitcoin/lib/litecoin_hardware_wallet_service.dart
Normal file
46
cw_bitcoin/lib/litecoin_hardware_wallet_service.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
|
|
1
cw_core/lib/unspent_coin_type.dart
Normal file
1
cw_core/lib/unspent_coin_type.dart
Normal file
|
@ -0,0 +1 @@
|
|||
enum UnspentCoinType { mweb, nonMweb, any }
|
|
@ -23,7 +23,7 @@ abstract class WalletAddresses {
|
|||
return _localAddress ?? address;
|
||||
}
|
||||
|
||||
String? get primaryAddress => null;
|
||||
String get primaryAddress => address;
|
||||
|
||||
String? _localAddress;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -264,7 +264,7 @@ abstract class EVMChainWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
_client.stop();
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
_updateFeesTimer?.cancel();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -116,7 +116,7 @@ class HavenWalletService extends WalletService<
|
|||
|
||||
if (!isValid) {
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close();
|
||||
wallet.close(shouldCleanup: false);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -137,7 +137,7 @@ class MoneroWalletService extends WalletService<
|
|||
|
||||
if (!isValid) {
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close();
|
||||
wallet.close(shouldCleanup: false);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
|
|||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@observable
|
||||
NanoAccount? account;
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -134,7 +134,7 @@ class WowneroWalletService extends WalletService<
|
|||
|
||||
if (!isValid) {
|
||||
await restoreOrResetWalletFiles(name);
|
||||
wallet.close();
|
||||
wallet.close(shouldCleanup: false);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -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, we’ll 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`, we’ll use it to identify the current node id.
|
||||
- Next, we’ll 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`, we’ll 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`
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
33
lib/di.dart
33
lib/di.dart
|
@ -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>(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
65
lib/entities/hardware_wallet/hardware_wallet_device.dart
Normal file
65
lib/entities/hardware_wallet/hardware_wallet_device.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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: (_) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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}');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ?? '',
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue