mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
Merge branch 'electrum-sp-refactors' into improve-sending-tx-for-electrum
This commit is contained in:
commit
5b60fb1f0c
119 changed files with 4938 additions and 2736 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -176,6 +176,7 @@ integration_test/playground.dart
|
||||||
|
|
||||||
# Monero.dart (Monero_C)
|
# Monero.dart (Monero_C)
|
||||||
scripts/monero_c
|
scripts/monero_c
|
||||||
|
scripts/android/app_env.fish
|
||||||
# iOS generated framework bin
|
# iOS generated framework bin
|
||||||
ios/MoneroWallet.framework/MoneroWallet
|
ios/MoneroWallet.framework/MoneroWallet
|
||||||
ios/WowneroWallet.framework/WowneroWallet
|
ios/WowneroWallet.framework/WowneroWallet
|
||||||
|
|
|
@ -5,7 +5,7 @@ Last modified: January 24, 2024
|
||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
|
|
||||||
Cake Labs LLC ("Cake Labs", "Company", or "We") respect your privacy and are committed to protecting it through our compliance with this policy.
|
Cake Labs LLC ("Cake Labs", "Company", or "We") respects your privacy and are committed to protecting it through our compliance with this policy.
|
||||||
|
|
||||||
This policy describes the types of information we may collect from you or that you may provide when you use the App (our "App") and our practices for collecting, using, maintaining, protecting, and disclosing that information.
|
This policy describes the types of information we may collect from you or that you may provide when you use the App (our "App") and our practices for collecting, using, maintaining, protecting, and disclosing that information.
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ Introduction
|
||||||
- On this App.
|
- On this App.
|
||||||
- In email, text, and other electronic messages between you and this App.
|
- In email, text, and other electronic messages between you and this App.
|
||||||
It does not apply to information collected by:
|
It does not apply to information collected by:
|
||||||
- Us offline or through any other means, including on any other App operated by Company or any third party (including our affiliates and subsidiaries); or
|
- Us offline or through any other means, including on any other App operated by the Company or any third party (including our affiliates and subsidiaries); or
|
||||||
- Any third party (including our affiliates and subsidiaries), including through any application or content (including advertising) that may link to or be accessible from or on the App.
|
- Any third party (including our affiliates and subsidiaries), including through any application or content (including advertising) that may link to or be accessible from or on the App.
|
||||||
Please read this policy carefully to understand our policies and practices regarding your information and how we will treat it. If you do not agree with our policies and practices, you have the choice to not use the App. By accessing or using this App, you agree to this privacy policy. This policy may change from time to time. Your continued use of this App after we make changes is deemed to be acceptance of those changes, so please check the policy periodically for updates.
|
Please read this policy carefully to understand our policies and practices regarding your information and how we will treat it. If you do not agree with our policies and practices, you have the choice to not use the App. By accessing or using this App, you agree to this privacy policy. This policy may change from time to time. Your continued use of this App after we make changes is deemed to be acceptance of those changes, so please check the policy periodically for updates.
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
# Cake Wallet
|
# Cake Wallet
|
||||||
|
|
||||||
Cake Wallet is an open source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux.
|
[Cake Wallet](https://cakewallet.com) is an open-source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux.
|
||||||
|
|
||||||
Cake Wallet includes support for several cryptocurrencies, including:
|
Cake Wallet includes support for several cryptocurrencies, including:
|
||||||
* Monero (XMR)
|
* Monero (XMR)
|
||||||
|
@ -26,7 +26,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
|
||||||
* Ethereum (ETH)
|
* Ethereum (ETH)
|
||||||
* Litecoin (LTC)
|
* Litecoin (LTC)
|
||||||
* Bitcoin Cash (BCH)
|
* Bitcoin Cash (BCH)
|
||||||
* Polygon (MATIC)
|
* Polygon (Pol)
|
||||||
* Solana (SOL)
|
* Solana (SOL)
|
||||||
* Nano (XNO)
|
* Nano (XNO)
|
||||||
* Haven (XHV)
|
* Haven (XHV)
|
||||||
|
@ -44,7 +44,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
|
||||||
* Create several wallets
|
* Create several wallets
|
||||||
* Select your own custom nodes/servers
|
* Select your own custom nodes/servers
|
||||||
* Address book
|
* Address book
|
||||||
* Backup to external location or iCloud
|
* Backup to an external location or iCloud
|
||||||
* Send to OpenAlias, Unstoppable Domains, Yats, and FIO Crypto Handles
|
* Send to OpenAlias, Unstoppable Domains, Yats, and FIO Crypto Handles
|
||||||
* Set desired network fee level
|
* Set desired network fee level
|
||||||
* Store local transaction notes
|
* Store local transaction notes
|
||||||
|
@ -161,7 +161,7 @@ The only parts to be translated, if needed, are the values m and s after the var
|
||||||
|
|
||||||
4. Add the language to `lib/entities/language_service.dart` under both `supportedLocales` and `localeCountryCode`. Use the name of the language in the local language and in English in parentheses after for `supportedLocales`. Use the [ISO 3166-1 alpha-3 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) for `localeCountryCode`. You must choose one country, so choose the country with the most native speakers of this language or is otherwise best associated with this language.
|
4. Add the language to `lib/entities/language_service.dart` under both `supportedLocales` and `localeCountryCode`. Use the name of the language in the local language and in English in parentheses after for `supportedLocales`. Use the [ISO 3166-1 alpha-3 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) for `localeCountryCode`. You must choose one country, so choose the country with the most native speakers of this language or is otherwise best associated with this language.
|
||||||
|
|
||||||
5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 letters localeCountryCode. The image must be 42x26 pixels with a 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make transparent. Or you can use another program like Photoshop.
|
5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 letters localeCountryCode. The image must be 42x26 pixels with 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make it transparent. Or you can use another program like Photoshop.
|
||||||
|
|
||||||
6. Add the new language code to `tool/utils/translation/translation_constants.dart`
|
6. Add the new language code to `tool/utils/translation/translation_constants.dart`
|
||||||
|
|
||||||
|
|
|
@ -9,4 +9,4 @@ If you need to report a vulnerability, please either:
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
As we don't maintain prevoius versions of the app, only the latest release for each platform is supported and any updates will bump the version number.
|
As we don't maintain previous versions of the app, only the latest release for each platform is supported and any updates will bump the version number.
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
<!-- required for API 18 - 30 -->
|
<!-- required for API 18 - 30 -->
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="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.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />
|
||||||
|
|
||||||
<!-- required for API <= 29 -->
|
<!-- required for API <= 29 -->
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||||
|
|
|
@ -55,7 +55,7 @@ Need to install flutter. For this please check section [How to install flutter o
|
||||||
|
|
||||||
### 3. Verify Installations
|
### 3. Verify Installations
|
||||||
|
|
||||||
Verify that the Flutter have been correctly installed on your system with the following command:
|
Verify that the Flutter has been correctly installed on your system with the following command:
|
||||||
|
|
||||||
`$ flutter doctor`
|
`$ flutter doctor`
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ And then export bundle:
|
||||||
|
|
||||||
`$ flatpak build-bundle export cake_wallet.flatpak com.cakewallet.CakeWallet`
|
`$ flatpak build-bundle export cake_wallet.flatpak com.cakewallet.CakeWallet`
|
||||||
|
|
||||||
Result file: `cake_wallet.flatpak` should be generated in current directory.
|
Result file: `cake_wallet.flatpak` should be generated in the current directory.
|
||||||
|
|
||||||
For install generated flatpak file use:
|
For install generated flatpak file use:
|
||||||
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
|
||||||
|
|
||||||
String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
|
|
||||||
try {
|
|
||||||
switch (script.getAddressType()) {
|
|
||||||
case P2pkhAddressType.p2pkh:
|
|
||||||
return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case P2shAddressType.p2pkInP2sh:
|
|
||||||
return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case SegwitAddresType.p2wpkh:
|
|
||||||
return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case P2shAddressType.p2pkhInP2sh:
|
|
||||||
return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case SegwitAddresType.p2wsh:
|
|
||||||
return P2wshAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case SegwitAddresType.p2tr:
|
|
||||||
return P2trAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
|
@ -1,34 +1,39 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
|
|
||||||
abstract class BaseBitcoinAddressRecord {
|
abstract class BaseBitcoinAddressRecord {
|
||||||
BaseBitcoinAddressRecord(
|
BaseBitcoinAddressRecord(
|
||||||
this.address, {
|
this.address, {
|
||||||
required this.index,
|
required this.index,
|
||||||
this.isHidden = false,
|
bool isChange = false,
|
||||||
int txCount = 0,
|
int txCount = 0,
|
||||||
int balance = 0,
|
int balance = 0,
|
||||||
String name = '',
|
String name = '',
|
||||||
bool isUsed = false,
|
bool isUsed = false,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.network,
|
bool? isHidden,
|
||||||
}) : _txCount = txCount,
|
}) : _txCount = txCount,
|
||||||
_balance = balance,
|
_balance = balance,
|
||||||
_name = name,
|
_name = name,
|
||||||
_isUsed = isUsed;
|
_isUsed = isUsed,
|
||||||
|
_isHidden = isHidden ?? isChange,
|
||||||
|
_isChange = isChange;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
|
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
|
||||||
|
|
||||||
final String address;
|
final String address;
|
||||||
bool isHidden;
|
bool _isHidden;
|
||||||
|
bool get isHidden => _isHidden;
|
||||||
|
final bool _isChange;
|
||||||
|
bool get isChange => _isChange;
|
||||||
final int index;
|
final int index;
|
||||||
int _txCount;
|
int _txCount;
|
||||||
int _balance;
|
int _balance;
|
||||||
String _name;
|
String _name;
|
||||||
bool _isUsed;
|
bool _isUsed;
|
||||||
BasedUtxoNetwork? network;
|
|
||||||
|
|
||||||
int get txCount => _txCount;
|
int get txCount => _txCount;
|
||||||
|
|
||||||
|
@ -42,7 +47,12 @@ abstract class BaseBitcoinAddressRecord {
|
||||||
|
|
||||||
bool get isUsed => _isUsed;
|
bool get isUsed => _isUsed;
|
||||||
|
|
||||||
void setAsUsed() => _isUsed = true;
|
void setAsUsed() {
|
||||||
|
_isUsed = true;
|
||||||
|
// TODO: check is hidden flow on addr list
|
||||||
|
_isHidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
void setNewName(String label) => _name = label;
|
void setNewName(String label) => _name = label;
|
||||||
|
|
||||||
int get hashCode => address.hashCode;
|
int get hashCode => address.hashCode;
|
||||||
|
@ -53,27 +63,43 @@ abstract class BaseBitcoinAddressRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
|
final BitcoinDerivationInfo derivationInfo;
|
||||||
|
final CWBitcoinDerivationType derivationType;
|
||||||
|
|
||||||
BitcoinAddressRecord(
|
BitcoinAddressRecord(
|
||||||
super.address, {
|
super.address, {
|
||||||
required super.index,
|
required super.index,
|
||||||
super.isHidden = false,
|
required this.derivationInfo,
|
||||||
|
required this.derivationType,
|
||||||
|
super.isHidden,
|
||||||
|
super.isChange = false,
|
||||||
super.txCount = 0,
|
super.txCount = 0,
|
||||||
super.balance = 0,
|
super.balance = 0,
|
||||||
super.name = '',
|
super.name = '',
|
||||||
super.isUsed = false,
|
super.isUsed = false,
|
||||||
required super.type,
|
required super.type,
|
||||||
String? scriptHash,
|
String? scriptHash,
|
||||||
required super.network,
|
BasedUtxoNetwork? network,
|
||||||
}) : scriptHash = scriptHash ??
|
}) {
|
||||||
(network != null ? BitcoinAddressUtils.scriptHash(address, network: network) : null);
|
if (scriptHash == null && network == null) {
|
||||||
|
throw ArgumentError('either scriptHash or network must be provided');
|
||||||
|
}
|
||||||
|
|
||||||
factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
|
this.scriptHash = scriptHash ?? BitcoinAddressUtils.scriptHash(address, network: network!);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
return BitcoinAddressRecord(
|
return BitcoinAddressRecord(
|
||||||
decoded['address'] as String,
|
decoded['address'] as String,
|
||||||
index: decoded['index'] as int,
|
index: decoded['index'] as int,
|
||||||
|
derivationInfo: BitcoinDerivationInfo.fromJSON(
|
||||||
|
decoded['derivationInfo'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
derivationType: CWBitcoinDerivationType.values[decoded['derivationType'] as int],
|
||||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||||
|
isChange: decoded['isChange'] as bool? ?? false,
|
||||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||||
txCount: decoded['txCount'] as int? ?? 0,
|
txCount: decoded['txCount'] as int? ?? 0,
|
||||||
name: decoded['name'] as String? ?? '',
|
name: decoded['name'] as String? ?? '',
|
||||||
|
@ -83,23 +109,19 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
.firstWhere((type) => type.toString() == decoded['type'] as String)
|
.firstWhere((type) => type.toString() == decoded['type'] as String)
|
||||||
: SegwitAddresType.p2wpkh,
|
: SegwitAddresType.p2wpkh,
|
||||||
scriptHash: decoded['scriptHash'] as String?,
|
scriptHash: decoded['scriptHash'] as String?,
|
||||||
network: network,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? scriptHash;
|
late String scriptHash;
|
||||||
|
|
||||||
String getScriptHash(BasedUtxoNetwork network) {
|
|
||||||
if (scriptHash != null) return scriptHash!;
|
|
||||||
scriptHash = BitcoinAddressUtils.scriptHash(address, network: network);
|
|
||||||
return scriptHash!;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'address': address,
|
'address': address,
|
||||||
'index': index,
|
'index': index,
|
||||||
|
'derivationInfo': derivationInfo.toJSON(),
|
||||||
|
'derivationType': derivationType.index,
|
||||||
'isHidden': isHidden,
|
'isHidden': isHidden,
|
||||||
|
'isChange': isChange,
|
||||||
'isUsed': isUsed,
|
'isUsed': isUsed,
|
||||||
'txCount': txCount,
|
'txCount': txCount,
|
||||||
'name': name,
|
'name': name,
|
||||||
|
@ -107,21 +129,51 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
'type': type.toString(),
|
'type': type.toString(),
|
||||||
'scriptHash': scriptHash,
|
'scriptHash': scriptHash,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other is BitcoinAddressRecord &&
|
||||||
|
other.address == address &&
|
||||||
|
other.index == index &&
|
||||||
|
other.derivationInfo == derivationInfo &&
|
||||||
|
other.scriptHash == scriptHash &&
|
||||||
|
other.type == type &&
|
||||||
|
other.derivationType == derivationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
address.hashCode ^
|
||||||
|
index.hashCode ^
|
||||||
|
derivationInfo.hashCode ^
|
||||||
|
scriptHash.hashCode ^
|
||||||
|
type.hashCode ^
|
||||||
|
derivationType.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
|
int get labelIndex => index;
|
||||||
|
final String? labelHex;
|
||||||
|
|
||||||
|
static bool isChangeAddress(int labelIndex) => labelIndex == 0;
|
||||||
|
|
||||||
BitcoinSilentPaymentAddressRecord(
|
BitcoinSilentPaymentAddressRecord(
|
||||||
super.address, {
|
super.address, {
|
||||||
required super.index,
|
required int labelIndex,
|
||||||
super.isHidden = false,
|
|
||||||
super.txCount = 0,
|
super.txCount = 0,
|
||||||
super.balance = 0,
|
super.balance = 0,
|
||||||
super.name = '',
|
super.name = '',
|
||||||
super.isUsed = false,
|
super.isUsed = false,
|
||||||
required this.silentPaymentTweak,
|
super.type = SilentPaymentsAddresType.p2sp,
|
||||||
required super.network,
|
super.isHidden,
|
||||||
required super.type,
|
this.labelHex,
|
||||||
}) : super();
|
}) : super(index: labelIndex, isChange: isChangeAddress(labelIndex)) {
|
||||||
|
if (labelIndex != 1 && labelHex == null) {
|
||||||
|
throw ArgumentError('label must be provided for silent address index != 1');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource,
|
factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource,
|
||||||
{BasedUtxoNetwork? network}) {
|
{BasedUtxoNetwork? network}) {
|
||||||
|
@ -129,36 +181,68 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
|
|
||||||
return BitcoinSilentPaymentAddressRecord(
|
return BitcoinSilentPaymentAddressRecord(
|
||||||
decoded['address'] as String,
|
decoded['address'] as String,
|
||||||
index: decoded['index'] as int,
|
labelIndex: decoded['labelIndex'] as int,
|
||||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
|
||||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||||
txCount: decoded['txCount'] as int? ?? 0,
|
txCount: decoded['txCount'] as int? ?? 0,
|
||||||
name: decoded['name'] as String? ?? '',
|
name: decoded['name'] as String? ?? '',
|
||||||
balance: decoded['balance'] as int? ?? 0,
|
balance: decoded['balance'] as int? ?? 0,
|
||||||
network: (decoded['network'] as String?) == null
|
labelHex: decoded['labelHex'] as String?,
|
||||||
? network
|
|
||||||
: BasedUtxoNetwork.fromName(decoded['network'] as String),
|
|
||||||
silentPaymentTweak: decoded['silent_payment_tweak'] as String?,
|
|
||||||
type: decoded['type'] != null && decoded['type'] != ''
|
|
||||||
? BitcoinAddressType.values
|
|
||||||
.firstWhere((type) => type.toString() == decoded['type'] as String)
|
|
||||||
: SilentPaymentsAddresType.p2sp,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String? silentPaymentTweak;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'address': address,
|
'address': address,
|
||||||
'index': index,
|
'labelIndex': labelIndex,
|
||||||
'isHidden': isHidden,
|
|
||||||
'isUsed': isUsed,
|
'isUsed': isUsed,
|
||||||
'txCount': txCount,
|
'txCount': txCount,
|
||||||
'name': name,
|
'name': name,
|
||||||
'balance': balance,
|
'balance': balance,
|
||||||
'type': type.toString(),
|
'type': type.toString(),
|
||||||
'network': network?.value,
|
'labelHex': labelHex,
|
||||||
'silent_payment_tweak': silentPaymentTweak,
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class BitcoinReceivedSPAddressRecord extends BitcoinSilentPaymentAddressRecord {
|
||||||
|
final ECPrivate spendKey;
|
||||||
|
|
||||||
|
BitcoinReceivedSPAddressRecord(
|
||||||
|
super.address, {
|
||||||
|
required super.labelIndex,
|
||||||
|
super.txCount = 0,
|
||||||
|
super.balance = 0,
|
||||||
|
super.name = '',
|
||||||
|
super.isUsed = false,
|
||||||
|
required this.spendKey,
|
||||||
|
super.type = SegwitAddresType.p2tr,
|
||||||
|
super.labelHex,
|
||||||
|
}) : super(isHidden: true);
|
||||||
|
|
||||||
|
factory BitcoinReceivedSPAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
|
||||||
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
|
return BitcoinReceivedSPAddressRecord(
|
||||||
|
decoded['address'] as String,
|
||||||
|
labelIndex: decoded['index'] as int,
|
||||||
|
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||||
|
txCount: decoded['txCount'] as int? ?? 0,
|
||||||
|
name: decoded['name'] as String? ?? '',
|
||||||
|
balance: decoded['balance'] as int? ?? 0,
|
||||||
|
labelHex: decoded['label'] as String?,
|
||||||
|
spendKey: ECPrivate.fromHex(decoded['spendKey'] as String),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toJSON() => json.encode({
|
||||||
|
'address': address,
|
||||||
|
'labelIndex': labelIndex,
|
||||||
|
'isUsed': isUsed,
|
||||||
|
'txCount': txCount,
|
||||||
|
'name': name,
|
||||||
|
'balance': balance,
|
||||||
|
'type': type.toString(),
|
||||||
|
'labelHex': labelHex,
|
||||||
|
'spend_key': spendKey.toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:cw_core/crypto_amount_format.dart';
|
|
||||||
|
|
||||||
const bitcoinAmountLength = 8;
|
|
||||||
const bitcoinAmountDivider = 100000000;
|
|
||||||
final bitcoinAmountFormat = NumberFormat()
|
|
||||||
..maximumFractionDigits = bitcoinAmountLength
|
|
||||||
..minimumFractionDigits = 1;
|
|
||||||
|
|
||||||
String bitcoinAmountToString({required int amount}) => bitcoinAmountFormat.format(
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
|
|
||||||
|
|
||||||
double bitcoinAmountToDouble({required int amount}) =>
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
|
|
||||||
|
|
||||||
int stringDoubleToBitcoinAmount(String amount) {
|
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
result = (double.parse(amount) * bitcoinAmountDivider).round();
|
|
||||||
} catch (e) {
|
|
||||||
result = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.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:cw_core/hardware/hardware_account_data.dart';
|
||||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||||
|
@ -12,8 +11,7 @@ class BitcoinHardwareWalletService {
|
||||||
|
|
||||||
final LedgerConnection ledgerConnection;
|
final LedgerConnection ledgerConnection;
|
||||||
|
|
||||||
Future<List<HardwareAccountData>> getAvailableAccounts(
|
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
|
||||||
{int index = 0, int limit = 5}) async {
|
|
||||||
final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
|
final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
|
||||||
|
|
||||||
final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
|
final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
|
||||||
|
@ -23,13 +21,14 @@ class BitcoinHardwareWalletService {
|
||||||
|
|
||||||
for (final i in indexRange) {
|
for (final i in indexRange) {
|
||||||
final derivationPath = "m/84'/0'/$i'";
|
final derivationPath = "m/84'/0'/$i'";
|
||||||
final xpub =
|
final xpub = await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
|
||||||
await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
|
final bip32 = Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
|
||||||
Bip32Slip10Secp256k1 hd =
|
|
||||||
Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
|
|
||||||
|
|
||||||
final address = generateP2WPKHAddress(
|
final fullPath = Bip32PathParser.parse(derivationPath).addElem(Bip32KeyIndex(0));
|
||||||
hd: hd, index: 0, network: BitcoinNetwork.mainnet);
|
|
||||||
|
final address = ECPublic.fromBip32(bip32.derive(fullPath).publicKey)
|
||||||
|
.toP2wpkhAddress()
|
||||||
|
.toAddress(BitcoinNetwork.mainnet);
|
||||||
|
|
||||||
accounts.add(HardwareAccountData(
|
accounts.add(HardwareAccountData(
|
||||||
address: address,
|
address: address,
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
|
||||||
import 'package:cw_core/output_info.dart';
|
import 'package:cw_core/output_info.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/unspent_coin_type.dart';
|
import 'package:cw_core/unspent_coin_type.dart';
|
||||||
|
|
||||||
class BitcoinTransactionCredentials {
|
class BitcoinTransactionCredentials {
|
||||||
BitcoinTransactionCredentials(this.outputs,
|
BitcoinTransactionCredentials(
|
||||||
{required this.priority, this.feeRate, this.coinTypeToSpendFrom = UnspentCoinType.any});
|
this.outputs, {
|
||||||
|
required this.priority,
|
||||||
|
this.feeRate,
|
||||||
|
this.coinTypeToSpendFrom = UnspentCoinType.any,
|
||||||
|
});
|
||||||
|
|
||||||
final List<OutputInfo> outputs;
|
final List<OutputInfo> outputs;
|
||||||
final BitcoinTransactionPriority? priority;
|
final TransactionPriority? priority;
|
||||||
final int? feeRate;
|
final int? feeRate;
|
||||||
final UnspentCoinType coinTypeToSpendFrom;
|
final UnspentCoinType coinTypeToSpendFrom;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,57 @@
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
|
||||||
class BitcoinTransactionPriority extends TransactionPriority {
|
class BitcoinTransactionPriority extends TransactionPriority {
|
||||||
const BitcoinTransactionPriority({required String title, required int raw})
|
const BitcoinTransactionPriority({required super.title, required super.raw});
|
||||||
: super(title: title, raw: raw);
|
|
||||||
|
|
||||||
static const List<BitcoinTransactionPriority> all = [fast, medium, slow, custom];
|
// Unimportant: the lowest possible, confirms when it confirms no matter how long it takes
|
||||||
static const BitcoinTransactionPriority slow =
|
static const BitcoinTransactionPriority unimportant =
|
||||||
BitcoinTransactionPriority(title: 'Slow', raw: 0);
|
BitcoinTransactionPriority(title: 'Unimportant', raw: 0);
|
||||||
static const BitcoinTransactionPriority medium =
|
// Normal: low fee, confirms in a reasonable time, normal because in most cases more than this is not needed, gets you in the next 2-3 blocks (about 1 hour)
|
||||||
BitcoinTransactionPriority(title: 'Medium', raw: 1);
|
static const BitcoinTransactionPriority normal =
|
||||||
static const BitcoinTransactionPriority fast =
|
BitcoinTransactionPriority(title: 'Normal', raw: 1);
|
||||||
BitcoinTransactionPriority(title: 'Fast', raw: 2);
|
// Elevated: medium fee, confirms soon, elevated because it's higher than normal, gets you in the next 1-2 blocks (about 30 mins)
|
||||||
|
static const BitcoinTransactionPriority elevated =
|
||||||
|
BitcoinTransactionPriority(title: 'Elevated', raw: 2);
|
||||||
|
// Priority: high fee, expected in the next block (about 10 mins).
|
||||||
|
static const BitcoinTransactionPriority priority =
|
||||||
|
BitcoinTransactionPriority(title: 'Priority', raw: 3);
|
||||||
|
// Custom: any fee, user defined
|
||||||
static const BitcoinTransactionPriority custom =
|
static const BitcoinTransactionPriority custom =
|
||||||
BitcoinTransactionPriority(title: 'Custom', raw: 3);
|
BitcoinTransactionPriority(title: 'Custom', raw: 4);
|
||||||
|
|
||||||
static BitcoinTransactionPriority deserialize({required int raw}) {
|
static BitcoinTransactionPriority deserialize({required int raw}) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
case 0:
|
case 0:
|
||||||
return slow;
|
return unimportant;
|
||||||
case 1:
|
case 1:
|
||||||
return medium;
|
return normal;
|
||||||
case 2:
|
case 2:
|
||||||
return fast;
|
return elevated;
|
||||||
case 3:
|
case 3:
|
||||||
|
return priority;
|
||||||
|
case 4:
|
||||||
return custom;
|
return custom;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for BitcoinTransactionPriority deserialize');
|
throw Exception('Unexpected token: $raw for TransactionPriority deserialize');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String get units => 'sat';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
var label = '';
|
var label = '';
|
||||||
|
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case BitcoinTransactionPriority.slow:
|
case BitcoinTransactionPriority.unimportant:
|
||||||
label = 'Slow ~24hrs+'; // '${S.current.transaction_priority_slow} ~24hrs';
|
label = 'Unimportant ~24hrs+'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||||
break;
|
break;
|
||||||
case BitcoinTransactionPriority.medium:
|
case BitcoinTransactionPriority.normal:
|
||||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
label = 'Normal ~1hr+'; // S.current.transaction_priority_medium;
|
||||||
break;
|
break;
|
||||||
case BitcoinTransactionPriority.fast:
|
case BitcoinTransactionPriority.elevated:
|
||||||
label = 'Fast';
|
label = 'Elevated';
|
||||||
|
break; // S.current.transaction_priority_fast;
|
||||||
|
case BitcoinTransactionPriority.priority:
|
||||||
|
label = 'Priority';
|
||||||
break; // S.current.transaction_priority_fast;
|
break; // S.current.transaction_priority_fast;
|
||||||
case BitcoinTransactionPriority.custom:
|
case BitcoinTransactionPriority.custom:
|
||||||
label = 'Custom';
|
label = 'Custom';
|
||||||
|
@ -61,19 +69,22 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LitecoinTransactionPriority extends BitcoinTransactionPriority {
|
class ElectrumTransactionPriority extends TransactionPriority {
|
||||||
const LitecoinTransactionPriority({required String title, required int raw})
|
const ElectrumTransactionPriority({required String title, required int raw})
|
||||||
: super(title: title, raw: raw);
|
: super(title: title, raw: raw);
|
||||||
|
|
||||||
static const List<LitecoinTransactionPriority> all = [fast, medium, slow];
|
static const List<ElectrumTransactionPriority> all = [fast, medium, slow, custom];
|
||||||
static const LitecoinTransactionPriority slow =
|
|
||||||
LitecoinTransactionPriority(title: 'Slow', raw: 0);
|
|
||||||
static const LitecoinTransactionPriority medium =
|
|
||||||
LitecoinTransactionPriority(title: 'Medium', raw: 1);
|
|
||||||
static const LitecoinTransactionPriority fast =
|
|
||||||
LitecoinTransactionPriority(title: 'Fast', raw: 2);
|
|
||||||
|
|
||||||
static LitecoinTransactionPriority deserialize({required int raw}) {
|
static const ElectrumTransactionPriority slow =
|
||||||
|
ElectrumTransactionPriority(title: 'Slow', raw: 0);
|
||||||
|
static const ElectrumTransactionPriority medium =
|
||||||
|
ElectrumTransactionPriority(title: 'Medium', raw: 1);
|
||||||
|
static const ElectrumTransactionPriority fast =
|
||||||
|
ElectrumTransactionPriority(title: 'Fast', raw: 2);
|
||||||
|
static const ElectrumTransactionPriority custom =
|
||||||
|
ElectrumTransactionPriority(title: 'Custom', raw: 3);
|
||||||
|
|
||||||
|
static ElectrumTransactionPriority deserialize({required int raw}) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
case 0:
|
case 0:
|
||||||
return slow;
|
return slow;
|
||||||
|
@ -81,27 +92,31 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority {
|
||||||
return medium;
|
return medium;
|
||||||
case 2:
|
case 2:
|
||||||
return fast;
|
return fast;
|
||||||
|
case 3:
|
||||||
|
return custom;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for LitecoinTransactionPriority deserialize');
|
throw Exception('Unexpected token: $raw for ElectrumTransactionPriority deserialize');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
String get units => 'sat';
|
||||||
String get units => 'Litoshi';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
var label = '';
|
var label = '';
|
||||||
|
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case LitecoinTransactionPriority.slow:
|
case ElectrumTransactionPriority.slow:
|
||||||
label = 'Slow'; // S.current.transaction_priority_slow;
|
label = 'Slow ~24hrs+'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||||
break;
|
break;
|
||||||
case LitecoinTransactionPriority.medium:
|
case ElectrumTransactionPriority.medium:
|
||||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
label = 'Medium'; // S.current.transaction_priority_medium;
|
||||||
break;
|
break;
|
||||||
case LitecoinTransactionPriority.fast:
|
case ElectrumTransactionPriority.fast:
|
||||||
label = 'Fast'; // S.current.transaction_priority_fast;
|
label = 'Fast';
|
||||||
|
break; // S.current.transaction_priority_fast;
|
||||||
|
case ElectrumTransactionPriority.custom:
|
||||||
|
label = 'Custom';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -110,54 +125,171 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority {
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String labelWithRate(int rate, int? customRate) {
|
||||||
|
final rateValue = this == custom ? customRate ??= 0 : rate;
|
||||||
|
return '${toString()} ($rateValue ${units}/byte)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
class BitcoinCashTransactionPriority extends BitcoinTransactionPriority {
|
|
||||||
const BitcoinCashTransactionPriority({required String title, required int raw})
|
|
||||||
: super(title: title, raw: raw);
|
|
||||||
|
|
||||||
static const List<BitcoinCashTransactionPriority> all = [fast, medium, slow];
|
class LitecoinTransactionPriority extends ElectrumTransactionPriority {
|
||||||
static const BitcoinCashTransactionPriority slow =
|
const LitecoinTransactionPriority({required super.title, required super.raw});
|
||||||
BitcoinCashTransactionPriority(title: 'Slow', raw: 0);
|
|
||||||
static const BitcoinCashTransactionPriority medium =
|
|
||||||
BitcoinCashTransactionPriority(title: 'Medium', raw: 1);
|
|
||||||
static const BitcoinCashTransactionPriority fast =
|
|
||||||
BitcoinCashTransactionPriority(title: 'Fast', raw: 2);
|
|
||||||
|
|
||||||
static BitcoinCashTransactionPriority deserialize({required int raw}) {
|
@override
|
||||||
switch (raw) {
|
String get units => 'lit';
|
||||||
case 0:
|
}
|
||||||
|
|
||||||
|
class BitcoinCashTransactionPriority extends ElectrumTransactionPriority {
|
||||||
|
const BitcoinCashTransactionPriority({required super.title, required super.raw});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get units => 'satoshi';
|
||||||
|
}
|
||||||
|
|
||||||
|
class BitcoinTransactionPriorities implements TransactionPriorities {
|
||||||
|
const BitcoinTransactionPriorities({
|
||||||
|
required this.unimportant,
|
||||||
|
required this.normal,
|
||||||
|
required this.elevated,
|
||||||
|
required this.priority,
|
||||||
|
required this.custom,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int unimportant;
|
||||||
|
final int normal;
|
||||||
|
final int elevated;
|
||||||
|
final int priority;
|
||||||
|
final int custom;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int operator [](TransactionPriority type) {
|
||||||
|
switch (type) {
|
||||||
|
case BitcoinTransactionPriority.unimportant:
|
||||||
|
return unimportant;
|
||||||
|
case BitcoinTransactionPriority.normal:
|
||||||
|
return normal;
|
||||||
|
case BitcoinTransactionPriority.elevated:
|
||||||
|
return elevated;
|
||||||
|
case BitcoinTransactionPriority.priority:
|
||||||
|
return priority;
|
||||||
|
case BitcoinTransactionPriority.custom:
|
||||||
|
return custom;
|
||||||
|
default:
|
||||||
|
throw Exception('Unexpected token: $type for TransactionPriorities operator[]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String labelWithRate(TransactionPriority priorityType, [int? rate]) {
|
||||||
|
late int rateValue;
|
||||||
|
|
||||||
|
if (priorityType == BitcoinTransactionPriority.custom) {
|
||||||
|
if (rate == null) {
|
||||||
|
throw Exception('Rate must be provided for custom transaction priority');
|
||||||
|
}
|
||||||
|
rateValue = rate;
|
||||||
|
} else {
|
||||||
|
rateValue = this[priorityType];
|
||||||
|
}
|
||||||
|
|
||||||
|
return '${priorityType.toString()} (${rateValue} ${priorityType.units}/byte)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, int> toJson() {
|
||||||
|
return {
|
||||||
|
'unimportant': unimportant,
|
||||||
|
'normal': normal,
|
||||||
|
'elevated': elevated,
|
||||||
|
'priority': priority,
|
||||||
|
'custom': custom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static BitcoinTransactionPriorities fromJson(Map<String, dynamic> json) {
|
||||||
|
return BitcoinTransactionPriorities(
|
||||||
|
unimportant: json['unimportant'] as int,
|
||||||
|
normal: json['normal'] as int,
|
||||||
|
elevated: json['elevated'] as int,
|
||||||
|
priority: json['priority'] as int,
|
||||||
|
custom: json['custom'] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumTransactionPriorities implements TransactionPriorities {
|
||||||
|
const ElectrumTransactionPriorities({
|
||||||
|
required this.slow,
|
||||||
|
required this.medium,
|
||||||
|
required this.fast,
|
||||||
|
required this.custom,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int slow;
|
||||||
|
final int medium;
|
||||||
|
final int fast;
|
||||||
|
final int custom;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int operator [](TransactionPriority type) {
|
||||||
|
switch (type) {
|
||||||
|
case ElectrumTransactionPriority.slow:
|
||||||
return slow;
|
return slow;
|
||||||
case 1:
|
case ElectrumTransactionPriority.medium:
|
||||||
return medium;
|
return medium;
|
||||||
case 2:
|
case ElectrumTransactionPriority.fast:
|
||||||
return fast;
|
return fast;
|
||||||
|
case ElectrumTransactionPriority.custom:
|
||||||
|
return custom;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for BitcoinCashTransactionPriority deserialize');
|
throw Exception('Unexpected token: $type for TransactionPriorities operator[]');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get units => 'Satoshi';
|
String labelWithRate(TransactionPriority priorityType, [int? rate]) {
|
||||||
|
return '${priorityType.toString()} (${this[priorityType]} ${priorityType.units}/byte)';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
factory ElectrumTransactionPriorities.fromList(List<int> list) {
|
||||||
String toString() {
|
if (list.length != 3) {
|
||||||
var label = '';
|
throw Exception(
|
||||||
|
'Unexpected list length: ${list.length} for BitcoinElectrumTransactionPriorities.fromList');
|
||||||
switch (this) {
|
|
||||||
case BitcoinCashTransactionPriority.slow:
|
|
||||||
label = 'Slow'; // S.current.transaction_priority_slow;
|
|
||||||
break;
|
|
||||||
case BitcoinCashTransactionPriority.medium:
|
|
||||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
|
||||||
break;
|
|
||||||
case BitcoinCashTransactionPriority.fast:
|
|
||||||
label = 'Fast'; // S.current.transaction_priority_fast;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return label;
|
return ElectrumTransactionPriorities(
|
||||||
|
slow: list[0],
|
||||||
|
medium: list[1],
|
||||||
|
fast: list[2],
|
||||||
|
custom: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, int> toJson() {
|
||||||
|
return {
|
||||||
|
'slow': slow,
|
||||||
|
'medium': medium,
|
||||||
|
'fast': fast,
|
||||||
|
'custom': custom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static ElectrumTransactionPriorities fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumTransactionPriorities(
|
||||||
|
slow: json['slow'] as int,
|
||||||
|
medium: json['medium'] as int,
|
||||||
|
fast: json['fast'] as int,
|
||||||
|
custom: json['custom'] as int,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransactionPriorities deserializeTransactionPriorities(Map<String, dynamic> json) {
|
||||||
|
if (json.containsKey('unimportant')) {
|
||||||
|
return BitcoinTransactionPriorities.fromJson(json);
|
||||||
|
} else if (json.containsKey('slow')) {
|
||||||
|
return ElectrumTransactionPriorities.fromJson(json);
|
||||||
|
} else {
|
||||||
|
throw Exception('Unexpected token: $json for deserializeTransactionPriorities');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_core/unspent_transaction_output.dart';
|
import 'package:cw_core/unspent_transaction_output.dart';
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
|
||||||
class BitcoinUnspent extends Unspent {
|
class BitcoinUnspent extends Unspent {
|
||||||
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
||||||
: bitcoinAddressRecord = addressRecord,
|
: bitcoinAddressRecord = addressRecord,
|
||||||
super(addressRecord.address, hash, value, vout, null);
|
super(addressRecord.address, hash, value, vout, null);
|
||||||
|
|
||||||
|
factory BitcoinUnspent.fromUTXO(BaseBitcoinAddressRecord address, ElectrumUtxo utxo) =>
|
||||||
|
BitcoinUnspent(address, utxo.txId, utxo.value.toInt(), utxo.vout);
|
||||||
|
|
||||||
factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) =>
|
factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) =>
|
||||||
BitcoinUnspent(
|
BitcoinUnspent(
|
||||||
address ?? BitcoinAddressRecord.fromJSON(json['address_record'].toString()),
|
address ?? BitcoinAddressRecord.fromJSON(json['address_record'].toString()),
|
||||||
json['tx_hash'] as String,
|
json['tx_hash'] as String,
|
||||||
json['value'] as int,
|
int.parse(json['value'].toString()),
|
||||||
json['tx_pos'] as int,
|
int.parse(json['tx_pos'].toString()),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
@ -25,43 +29,13 @@ class BitcoinUnspent extends Unspent {
|
||||||
}
|
}
|
||||||
|
|
||||||
final BaseBitcoinAddressRecord bitcoinAddressRecord;
|
final BaseBitcoinAddressRecord bitcoinAddressRecord;
|
||||||
}
|
|
||||||
|
|
||||||
class BitcoinSilentPaymentsUnspent extends BitcoinUnspent {
|
|
||||||
BitcoinSilentPaymentsUnspent(
|
|
||||||
BitcoinSilentPaymentAddressRecord addressRecord,
|
|
||||||
String hash,
|
|
||||||
int value,
|
|
||||||
int vout, {
|
|
||||||
required this.silentPaymentTweak,
|
|
||||||
required this.silentPaymentLabel,
|
|
||||||
}) : super(addressRecord, hash, value, vout);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
factory BitcoinSilentPaymentsUnspent.fromJSON(
|
bool operator ==(Object o) {
|
||||||
BitcoinSilentPaymentAddressRecord? address, Map<String, dynamic> json) =>
|
if (identical(this, o)) return true;
|
||||||
BitcoinSilentPaymentsUnspent(
|
return o is BitcoinUnspent && hash == o.hash && vout == o.vout;
|
||||||
address ?? BitcoinSilentPaymentAddressRecord.fromJSON(json['address_record'].toString()),
|
|
||||||
json['tx_hash'] as String,
|
|
||||||
json['value'] as int,
|
|
||||||
json['tx_pos'] as int,
|
|
||||||
silentPaymentTweak: json['silent_payment_tweak'] as String?,
|
|
||||||
silentPaymentLabel: json['silent_payment_label'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{
|
|
||||||
'address_record': bitcoinAddressRecord.toJSON(),
|
|
||||||
'tx_hash': hash,
|
|
||||||
'value': value,
|
|
||||||
'tx_pos': vout,
|
|
||||||
'silent_payment_tweak': silentPaymentTweak,
|
|
||||||
'silent_payment_label': silentPaymentLabel,
|
|
||||||
};
|
|
||||||
return json;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String? silentPaymentTweak;
|
@override
|
||||||
String? silentPaymentLabel;
|
int get hashCode => Object.hash(hash, vout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
|
||||||
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||||
|
@ -13,26 +18,33 @@ import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/get_height_by_date.dart';
|
||||||
|
import 'package:cw_core/sync_status.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_keys_file.dart';
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:sp_scanner/sp_scanner.dart';
|
||||||
|
|
||||||
part 'bitcoin_wallet.g.dart';
|
part 'bitcoin_wallet.g.dart';
|
||||||
|
|
||||||
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
||||||
|
|
||||||
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
StreamSubscription<dynamic>? _receiveStream;
|
||||||
|
|
||||||
BitcoinWalletBase({
|
BitcoinWalletBase({
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
Uint8List? seedBytes,
|
List<int>? seedBytes,
|
||||||
String? mnemonic,
|
String? mnemonic,
|
||||||
String? xpub,
|
String? xpub,
|
||||||
String? addressPageType,
|
String? addressPageType,
|
||||||
|
@ -45,6 +57,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||||
int initialSilentAddressIndex = 0,
|
int initialSilentAddressIndex = 0,
|
||||||
bool? alwaysScan,
|
bool? alwaysScan,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
|
super.hdWallets,
|
||||||
}) : super(
|
}) : super(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
|
@ -61,16 +75,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
encryptionFileUtils: encryptionFileUtils,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
currency: networkParam == BitcoinNetwork.testnet
|
currency:
|
||||||
? CryptoCurrency.tbtc
|
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
|
||||||
: CryptoCurrency.btc,
|
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
) {
|
) {
|
||||||
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
|
|
||||||
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
|
|
||||||
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
|
||||||
// String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
|
|
||||||
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
|
||||||
walletAddresses = BitcoinWalletAddresses(
|
walletAddresses = BitcoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
|
@ -78,17 +87,13 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
initialSilentAddresses: initialSilentAddresses,
|
initialSilentAddresses: initialSilentAddresses,
|
||||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||||
mainHd: hd,
|
|
||||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
|
||||||
network: networkParam ?? network,
|
network: networkParam ?? network,
|
||||||
masterHd:
|
|
||||||
seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
|
|
||||||
isHardwareWallet: walletInfo.isHardwareWallet,
|
isHardwareWallet: walletInfo.isHardwareWallet,
|
||||||
|
hdWallets: hdWallets,
|
||||||
);
|
);
|
||||||
|
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
this.walletAddresses.isEnabledAutoGenerateSubaddress =
|
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||||
this.isEnabledAutoGenerateSubaddress;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,21 +112,38 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
Map<String, int>? initialRegularAddressIndex,
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
Map<String, int>? initialChangeAddressIndex,
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
int initialSilentAddressIndex = 0,
|
int initialSilentAddressIndex = 0,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
late Uint8List seedBytes;
|
late List<int> seedBytes;
|
||||||
|
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets = {};
|
||||||
|
|
||||||
switch (walletInfo.derivationInfo?.derivationType) {
|
for (final derivation in walletInfo.derivations ?? <DerivationInfo>[]) {
|
||||||
case DerivationType.bip39:
|
if (derivation.derivationType == DerivationType.bip39) {
|
||||||
seedBytes = await bip39.mnemonicToSeed(
|
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
|
||||||
mnemonic,
|
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
|
||||||
passphrase: passphrase ?? "",
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case DerivationType.electrum:
|
} else {
|
||||||
default:
|
try {
|
||||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
|
||||||
|
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
|
||||||
|
} catch (e) {
|
||||||
|
print("electrum_v2 seed error: $e");
|
||||||
|
|
||||||
|
try {
|
||||||
|
seedBytes = ElectrumV1SeedGenerator(mnemonic).generate();
|
||||||
|
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
|
||||||
|
} catch (e) {
|
||||||
|
print("electrum_v1 seed error: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hdWallets[CWBitcoinDerivationType.old] =
|
||||||
|
hdWallets[CWBitcoinDerivationType.bip39] ?? hdWallets[CWBitcoinDerivationType.electrum]!;
|
||||||
|
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
passphrase: passphrase ?? "",
|
passphrase: passphrase ?? "",
|
||||||
|
@ -138,6 +160,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
addressPageType: addressPageType,
|
addressPageType: addressPageType,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
|
hdWallets: hdWallets,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +172,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
required bool alwaysScan,
|
required bool alwaysScan,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
final network = walletInfo.network != null
|
final network = walletInfo.network != null
|
||||||
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
||||||
|
@ -189,28 +214,43 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
walletInfo.derivationInfo ??= DerivationInfo();
|
walletInfo.derivationInfo ??= DerivationInfo();
|
||||||
|
|
||||||
// set the default if not present:
|
// set the default if not present:
|
||||||
walletInfo.derivationInfo!.derivationPath ??=
|
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
|
||||||
snp?.derivationPath ?? electrum_path;
|
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
|
||||||
walletInfo.derivationInfo!.derivationType ??=
|
|
||||||
snp?.derivationType ?? DerivationType.electrum;
|
|
||||||
|
|
||||||
Uint8List? seedBytes = null;
|
List<int>? seedBytes = null;
|
||||||
|
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets = {};
|
||||||
final mnemonic = keysData.mnemonic;
|
final mnemonic = keysData.mnemonic;
|
||||||
final passphrase = keysData.passphrase;
|
final passphrase = keysData.passphrase;
|
||||||
|
|
||||||
if (mnemonic != null) {
|
if (mnemonic != null) {
|
||||||
switch (walletInfo.derivationInfo!.derivationType) {
|
for (final derivation in walletInfo.derivations ?? <DerivationInfo>[]) {
|
||||||
case DerivationType.electrum:
|
if (derivation.derivationType == DerivationType.bip39) {
|
||||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
|
||||||
|
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case DerivationType.bip39:
|
} else {
|
||||||
default:
|
try {
|
||||||
seedBytes = await bip39.mnemonicToSeed(
|
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
|
||||||
mnemonic,
|
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
|
||||||
passphrase: passphrase ?? '',
|
} catch (e) {
|
||||||
);
|
print("electrum_v2 seed error: $e");
|
||||||
|
|
||||||
|
try {
|
||||||
|
seedBytes = ElectrumV1SeedGenerator(mnemonic).generate();
|
||||||
|
hdWallets[CWBitcoinDerivationType.electrum] =
|
||||||
|
Bip32Slip10Secp256k1.fromSeed(seedBytes);
|
||||||
|
} catch (e) {
|
||||||
|
print("electrum_v1 seed error: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hdWallets[CWBitcoinDerivationType.old] =
|
||||||
|
hdWallets[CWBitcoinDerivationType.bip39] ?? hdWallets[CWBitcoinDerivationType.electrum]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
|
@ -231,9 +271,59 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
addressPageType: snp?.addressPageType,
|
addressPageType: snp?.addressPageType,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
|
hdWallets: hdWallets,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> getNodeIsElectrs() async {
|
||||||
|
final version = await sendWorker(ElectrumWorkerGetVersionRequest()) as List<String>;
|
||||||
|
|
||||||
|
if (version.isNotEmpty) {
|
||||||
|
final server = version[0];
|
||||||
|
|
||||||
|
if (server.toLowerCase().contains('electrs')) {
|
||||||
|
node!.isElectrs = true;
|
||||||
|
node!.save();
|
||||||
|
return node!.isElectrs!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node!.isElectrs = false;
|
||||||
|
node!.save();
|
||||||
|
return node!.isElectrs!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> getNodeSupportsSilentPayments() async {
|
||||||
|
return true;
|
||||||
|
// As of today (august 2024), only ElectrumRS supports silent payments
|
||||||
|
// if (!(await getNodeIsElectrs())) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (node == null) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// final tweaksResponse = await electrumClient.getTweaks(height: 0);
|
||||||
|
|
||||||
|
// if (tweaksResponse != null) {
|
||||||
|
// node!.supportsSilentPayments = true;
|
||||||
|
// node!.save();
|
||||||
|
// return node!.supportsSilentPayments!;
|
||||||
|
// }
|
||||||
|
// } on RequestFailedTimeoutException catch (_) {
|
||||||
|
// node!.supportsSilentPayments = false;
|
||||||
|
// node!.save();
|
||||||
|
// return node!.supportsSilentPayments!;
|
||||||
|
// } catch (_) {}
|
||||||
|
|
||||||
|
// node!.supportsSilentPayments = false;
|
||||||
|
// node!.save();
|
||||||
|
// return node!.supportsSilentPayments!;
|
||||||
|
}
|
||||||
|
|
||||||
LedgerConnection? _ledgerConnection;
|
LedgerConnection? _ledgerConnection;
|
||||||
BitcoinLedgerApp? _bitcoinLedgerApp;
|
BitcoinLedgerApp? _bitcoinLedgerApp;
|
||||||
|
|
||||||
|
@ -261,9 +351,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
|
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
|
||||||
for (final utxo in utxos) {
|
for (final utxo in utxos) {
|
||||||
final rawTx =
|
final rawTx =
|
||||||
await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
|
(await getTransactionExpanded(hash: utxo.utxo.txHash)).originalTransaction.toHex();
|
||||||
final publicKeyAndDerivationPath =
|
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
||||||
publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
|
||||||
|
|
||||||
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
||||||
utxo: utxo.utxo,
|
utxo: utxo.utxo,
|
||||||
|
@ -275,8 +364,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
final psbt = PSBTTransactionBuild(
|
final psbt =
|
||||||
inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
|
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
|
||||||
|
|
||||||
final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt);
|
final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt);
|
||||||
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
|
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
|
||||||
|
@ -286,20 +375,443 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
Future<String> signMessage(String message, {String? address = null}) async {
|
Future<String> signMessage(String message, {String? address = null}) async {
|
||||||
if (walletInfo.isHardwareWallet) {
|
if (walletInfo.isHardwareWallet) {
|
||||||
final addressEntry = address != null
|
final addressEntry = address != null
|
||||||
? walletAddresses.allAddresses
|
? walletAddresses.allAddresses.firstWhere((element) => element.address == address)
|
||||||
.firstWhere((element) => element.address == address)
|
|
||||||
: null;
|
: null;
|
||||||
final index = addressEntry?.index ?? 0;
|
final index = addressEntry?.index ?? 0;
|
||||||
final isChange = addressEntry?.isHidden == true ? 1 : 0;
|
final isChange = addressEntry?.isChange == true ? 1 : 0;
|
||||||
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
||||||
final derivationPath =
|
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
|
||||||
accountPath != null ? "$accountPath/$isChange/$index" : null;
|
|
||||||
|
|
||||||
final signature = await _bitcoinLedgerApp!.signMessage(
|
final signature = await _bitcoinLedgerApp!
|
||||||
message: ascii.encode(message), signDerivationPath: derivationPath);
|
.signMessage(message: ascii.encode(message), signDerivationPath: derivationPath);
|
||||||
return base64Encode(signature);
|
return base64Encode(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.signMessage(message, address: address);
|
return super.signMessage(message, address: address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> setSilentPaymentsScanning(bool active) async {
|
||||||
|
silentPaymentsScanningActive = active;
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
syncStatus = AttemptingScanSyncStatus();
|
||||||
|
|
||||||
|
final tip = currentChainTip!;
|
||||||
|
|
||||||
|
if (tip == walletInfo.restoreHeight) {
|
||||||
|
syncStatus = SyncedTipSyncStatus(tip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tip > walletInfo.restoreHeight) {
|
||||||
|
_setListeners(walletInfo.restoreHeight);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alwaysScan = false;
|
||||||
|
|
||||||
|
// _isolate?.then((value) => value.kill(priority: Isolate.immediate));
|
||||||
|
|
||||||
|
// if (rpc!.isConnected) {
|
||||||
|
// syncStatus = SyncedSyncStatus();
|
||||||
|
// } else {
|
||||||
|
// syncStatus = NotConnectedSyncStatus();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// @action
|
||||||
|
// Future<void> updateAllUnspents() async {
|
||||||
|
// List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||||
|
|
||||||
|
// // Update unspents stored from scanned silent payment transactions
|
||||||
|
// transactionHistory.transactions.values.forEach((tx) {
|
||||||
|
// if (tx.unspents != null) {
|
||||||
|
// updatedUnspentCoins.addAll(tx.unspents!);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // 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
|
||||||
|
// .where((element) => element.type != SegwitAddresType.mweb)
|
||||||
|
// .map((address) async {
|
||||||
|
// updatedUnspentCoins.addAll(await fetchUnspent(address));
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// unspentCoins.addAll(updatedUnspentCoins);
|
||||||
|
|
||||||
|
// if (unspentCoinsInfo.length != updatedUnspentCoins.length) {
|
||||||
|
// unspentCoins.forEach((coin) => addCoinInfo(coin));
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await updateCoins(unspentCoins.toSet());
|
||||||
|
// await refreshUnspentCoinsInfo();
|
||||||
|
// }
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateCoin(BitcoinUnspent coin) {
|
||||||
|
final coinInfoList = unspentCoinsInfo.values.where(
|
||||||
|
(element) =>
|
||||||
|
element.walletId.contains(id) &&
|
||||||
|
element.hash.contains(coin.hash) &&
|
||||||
|
element.vout == coin.vout,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (coinInfoList.isNotEmpty) {
|
||||||
|
final coinInfo = coinInfoList.first;
|
||||||
|
|
||||||
|
coin.isFrozen = coinInfo.isFrozen;
|
||||||
|
coin.isSending = coinInfo.isSending;
|
||||||
|
coin.note = coinInfo.note;
|
||||||
|
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
||||||
|
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
||||||
|
} else {
|
||||||
|
addCoinInfo(coin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> startSync() async {
|
||||||
|
await _setInitialScanHeight();
|
||||||
|
|
||||||
|
await super.startSync();
|
||||||
|
|
||||||
|
if (alwaysScan == true) {
|
||||||
|
_setListeners(walletInfo.restoreHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> rescan({required int height, bool? doSingleScan}) async {
|
||||||
|
silentPaymentsScanningActive = true;
|
||||||
|
_setListeners(height, doSingleScan: doSingleScan);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @action
|
||||||
|
// Future<void> registerSilentPaymentsKey(bool register) async {
|
||||||
|
// silentPaymentsScanningActive = active;
|
||||||
|
|
||||||
|
// if (active) {
|
||||||
|
// syncStatus = AttemptingScanSyncStatus();
|
||||||
|
|
||||||
|
// final tip = await getUpdatedChainTip();
|
||||||
|
|
||||||
|
// if (tip == walletInfo.restoreHeight) {
|
||||||
|
// syncStatus = SyncedTipSyncStatus(tip);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (tip > walletInfo.restoreHeight) {
|
||||||
|
// _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// alwaysScan = false;
|
||||||
|
|
||||||
|
// _isolate?.then((value) => value.kill(priority: Isolate.immediate));
|
||||||
|
|
||||||
|
// if (electrumClient.isConnected) {
|
||||||
|
// syncStatus = SyncedSyncStatus();
|
||||||
|
// } else {
|
||||||
|
// syncStatus = NotConnectedSyncStatus();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> registerSilentPaymentsKey() async {
|
||||||
|
// final registered = await electrumClient.tweaksRegister(
|
||||||
|
// secViewKey: walletAddresses.silentAddress!.b_scan.toHex(),
|
||||||
|
// pubSpendKey: walletAddresses.silentAddress!.B_spend.toHex(),
|
||||||
|
// labels: walletAddresses.silentAddresses
|
||||||
|
// .where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.labelIndex >= 1)
|
||||||
|
// .map((addr) => addr.labelIndex)
|
||||||
|
// .toList(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// print("registered: $registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void _updateSilentAddressRecord(BitcoinUnspent unspent) {
|
||||||
|
final receiveAddressRecord = unspent.bitcoinAddressRecord as BitcoinReceivedSPAddressRecord;
|
||||||
|
final silentAddress = walletAddresses.silentAddress!;
|
||||||
|
final silentPaymentAddress = SilentPaymentAddress(
|
||||||
|
version: silentAddress.version,
|
||||||
|
B_scan: silentAddress.B_scan,
|
||||||
|
B_spend: receiveAddressRecord.labelHex != null
|
||||||
|
? silentAddress.B_spend.tweakAdd(
|
||||||
|
BigintUtils.fromBytes(BytesUtils.fromHexString(receiveAddressRecord.labelHex!)),
|
||||||
|
)
|
||||||
|
: silentAddress.B_spend,
|
||||||
|
);
|
||||||
|
|
||||||
|
final addressRecord = walletAddresses.silentAddresses
|
||||||
|
.firstWhere((address) => address.address == silentPaymentAddress.toString());
|
||||||
|
addressRecord.txCount += 1;
|
||||||
|
addressRecord.balance += unspent.value;
|
||||||
|
|
||||||
|
walletAddresses.addSilentAddresses(
|
||||||
|
[unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@action
|
||||||
|
Future<void> handleWorkerResponse(dynamic message) async {
|
||||||
|
super.handleWorkerResponse(message);
|
||||||
|
|
||||||
|
Map<String, dynamic> messageJson;
|
||||||
|
if (message is String) {
|
||||||
|
messageJson = jsonDecode(message) as Map<String, dynamic>;
|
||||||
|
} else {
|
||||||
|
messageJson = message as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
final workerMethod = messageJson['method'] as String;
|
||||||
|
|
||||||
|
switch (workerMethod) {
|
||||||
|
case ElectrumRequestMethods.tweaksSubscribeMethod:
|
||||||
|
final response = ElectrumWorkerTweaksSubscribeResponse.fromJson(messageJson);
|
||||||
|
onTweaksSyncResponse(response.result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> onTweaksSyncResponse(TweaksSyncResponse result) async {
|
||||||
|
if (result.transactions?.isNotEmpty == true) {
|
||||||
|
for (final map in result.transactions!.entries) {
|
||||||
|
final txid = map.key;
|
||||||
|
final tx = map.value;
|
||||||
|
|
||||||
|
if (tx.unspents != null) {
|
||||||
|
final existingTxInfo = transactionHistory.transactions[txid];
|
||||||
|
final txAlreadyExisted = existingTxInfo != null;
|
||||||
|
|
||||||
|
// Updating tx after re-scanned
|
||||||
|
if (txAlreadyExisted) {
|
||||||
|
existingTxInfo.amount = tx.amount;
|
||||||
|
existingTxInfo.confirmations = tx.confirmations;
|
||||||
|
existingTxInfo.height = tx.height;
|
||||||
|
|
||||||
|
final newUnspents = tx.unspents!
|
||||||
|
.where((unspent) => !(existingTxInfo.unspents?.any((element) =>
|
||||||
|
element.hash.contains(unspent.hash) &&
|
||||||
|
element.vout == unspent.vout &&
|
||||||
|
element.value == unspent.value) ??
|
||||||
|
false))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (newUnspents.isNotEmpty) {
|
||||||
|
newUnspents.forEach(_updateSilentAddressRecord);
|
||||||
|
|
||||||
|
existingTxInfo.unspents ??= [];
|
||||||
|
existingTxInfo.unspents!.addAll(newUnspents);
|
||||||
|
|
||||||
|
final newAmount = newUnspents.length > 1
|
||||||
|
? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent)
|
||||||
|
: newUnspents[0].value;
|
||||||
|
|
||||||
|
if (existingTxInfo.direction == TransactionDirection.incoming) {
|
||||||
|
existingTxInfo.amount += newAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates existing TX
|
||||||
|
transactionHistory.addOne(existingTxInfo);
|
||||||
|
// Update balance record
|
||||||
|
balance[currency]!.confirmed += newAmount;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// else: First time seeing this TX after scanning
|
||||||
|
tx.unspents!.forEach(_updateSilentAddressRecord);
|
||||||
|
|
||||||
|
// Add new TX record
|
||||||
|
transactionHistory.addOne(tx);
|
||||||
|
// Update balance record
|
||||||
|
balance[currency]!.confirmed += tx.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateAllUnspents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final newSyncStatus = result.syncStatus;
|
||||||
|
|
||||||
|
if (newSyncStatus != null) {
|
||||||
|
if (newSyncStatus is UnsupportedSyncStatus) {
|
||||||
|
nodeSupportsSilentPayments = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSyncStatus is SyncingSyncStatus) {
|
||||||
|
syncStatus = SyncingSyncStatus(newSyncStatus.blocksLeft, newSyncStatus.ptc);
|
||||||
|
} else {
|
||||||
|
syncStatus = newSyncStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
await walletInfo.updateRestoreHeight(result.height!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> _setListeners(int height, {bool? doSingleScan}) async {
|
||||||
|
if (currentChainTip == null) {
|
||||||
|
throw Exception("currentChainTip is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
final chainTip = currentChainTip!;
|
||||||
|
|
||||||
|
if (chainTip == height) {
|
||||||
|
syncStatus = SyncedSyncStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncStatus = AttemptingScanSyncStatus();
|
||||||
|
|
||||||
|
workerSendPort!.send(
|
||||||
|
ElectrumWorkerTweaksSubscribeRequest(
|
||||||
|
scanData: ScanData(
|
||||||
|
silentAddress: walletAddresses.silentAddress!,
|
||||||
|
network: network,
|
||||||
|
height: height,
|
||||||
|
chainTip: chainTip,
|
||||||
|
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
|
||||||
|
labels: walletAddresses.labels,
|
||||||
|
labelIndexes: walletAddresses.silentAddresses
|
||||||
|
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.labelIndex >= 1)
|
||||||
|
.map((addr) => addr.labelIndex)
|
||||||
|
.toList(),
|
||||||
|
isSingleScan: doSingleScan ?? false,
|
||||||
|
),
|
||||||
|
).toJson(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@action
|
||||||
|
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||||
|
throw UnimplementedError();
|
||||||
|
// try {
|
||||||
|
// final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||||
|
|
||||||
|
// await Future.wait(
|
||||||
|
// BITCOIN_ADDRESS_TYPES.map(
|
||||||
|
// (type) => fetchTransactionsForAddressType(historiesWithDetails, type),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// transactionHistory.transactions.values.forEach((tx) async {
|
||||||
|
// final isPendingSilentPaymentUtxo =
|
||||||
|
// (tx.isPending || tx.confirmations == 0) && historiesWithDetails[tx.id] == null;
|
||||||
|
|
||||||
|
// if (isPendingSilentPaymentUtxo) {
|
||||||
|
// final info = await fetchTransactionInfo(hash: tx.id, height: tx.height);
|
||||||
|
|
||||||
|
// if (info != null) {
|
||||||
|
// tx.confirmations = info.confirmations;
|
||||||
|
// tx.isPending = tx.confirmations == 0;
|
||||||
|
// transactionHistory.addOne(tx);
|
||||||
|
// await transactionHistory.save();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return historiesWithDetails;
|
||||||
|
// } catch (e) {
|
||||||
|
// print("fetchTransactions $e");
|
||||||
|
// return {};
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@action
|
||||||
|
Future<void> updateTransactions([List<BitcoinAddressRecord>? addresses]) async {
|
||||||
|
super.updateTransactions();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// @action
|
||||||
|
// Future<ElectrumBalance> fetchBalances() async {
|
||||||
|
// final balance = await super.fetchBalances();
|
||||||
|
|
||||||
|
// int totalFrozen = balance.frozen;
|
||||||
|
// int totalConfirmed = balance.confirmed;
|
||||||
|
|
||||||
|
// // Add values from unspent coins that are not fetched by the address list
|
||||||
|
// // i.e. scanned silent payments
|
||||||
|
// transactionHistory.transactions.values.forEach((tx) {
|
||||||
|
// if (tx.unspents != null) {
|
||||||
|
// tx.unspents!.forEach((unspent) {
|
||||||
|
// if (unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||||
|
// if (unspent.isFrozen) totalFrozen += unspent.value;
|
||||||
|
// totalConfirmed += unspent.value;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return ElectrumBalance(
|
||||||
|
// confirmed: totalConfirmed,
|
||||||
|
// unconfirmed: balance.unconfirmed,
|
||||||
|
// frozen: totalFrozen,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
@override
|
||||||
|
@action
|
||||||
|
Future<void> onHeadersResponse(ElectrumHeaderResponse response) async {
|
||||||
|
super.onHeadersResponse(response);
|
||||||
|
|
||||||
|
_setInitialScanHeight();
|
||||||
|
|
||||||
|
// New headers received, start scanning
|
||||||
|
if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
|
||||||
|
_setListeners(walletInfo.restoreHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setInitialScanHeight() async {
|
||||||
|
final validChainTip = currentChainTip != null && currentChainTip != 0;
|
||||||
|
if (validChainTip && walletInfo.restoreHeight == 0) {
|
||||||
|
await walletInfo.updateRestoreHeight(currentChainTip!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _hardenedDerivationPath(String derivationPath) =>
|
||||||
|
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@action
|
||||||
|
void syncStatusReaction(SyncStatus syncStatus) {
|
||||||
|
switch (syncStatus.runtimeType) {
|
||||||
|
case SyncingSyncStatus:
|
||||||
|
return;
|
||||||
|
case SyncedTipSyncStatus:
|
||||||
|
// Message is shown on the UI for 3 seconds, then reverted to synced
|
||||||
|
Timer(Duration(seconds: 3), () {
|
||||||
|
if (this.syncStatus is SyncedTipSyncStatus) this.syncStatus = SyncedSyncStatus();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
super.syncStatusReaction(syncStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/bip/bip/bip32/bip32.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
@ -12,33 +11,101 @@ class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAd
|
||||||
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||||
BitcoinWalletAddressesBase(
|
BitcoinWalletAddressesBase(
|
||||||
WalletInfo walletInfo, {
|
WalletInfo walletInfo, {
|
||||||
required super.mainHd,
|
|
||||||
required super.sideHd,
|
|
||||||
required super.network,
|
required super.network,
|
||||||
required super.isHardwareWallet,
|
required super.isHardwareWallet,
|
||||||
|
required super.hdWallets,
|
||||||
super.initialAddresses,
|
super.initialAddresses,
|
||||||
super.initialRegularAddressIndex,
|
super.initialRegularAddressIndex,
|
||||||
super.initialChangeAddressIndex,
|
super.initialChangeAddressIndex,
|
||||||
super.initialSilentAddresses,
|
super.initialSilentAddresses,
|
||||||
super.initialSilentAddressIndex = 0,
|
super.initialSilentAddressIndex = 0,
|
||||||
super.masterHd,
|
|
||||||
}) : super(walletInfo);
|
}) : super(walletInfo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress(
|
Future<void> init() async {
|
||||||
{required int index, required Bip32Slip10Secp256k1 hd, BitcoinAddressType? addressType}) {
|
await generateInitialAddresses(type: SegwitAddresType.p2wpkh);
|
||||||
if (addressType == P2pkhAddressType.p2pkh)
|
|
||||||
return generateP2PKHAddress(hd: hd, index: index, network: network);
|
|
||||||
|
|
||||||
if (addressType == SegwitAddresType.p2tr)
|
if (!isHardwareWallet) {
|
||||||
return generateP2TRAddress(hd: hd, index: index, network: network);
|
await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||||
|
await generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
|
||||||
|
await generateInitialAddresses(type: SegwitAddresType.p2tr);
|
||||||
|
await generateInitialAddresses(type: SegwitAddresType.p2wsh);
|
||||||
|
}
|
||||||
|
|
||||||
if (addressType == SegwitAddresType.p2wsh)
|
await updateAddressesInBox();
|
||||||
return generateP2WSHAddress(hd: hd, index: index, network: network);
|
}
|
||||||
|
|
||||||
if (addressType == P2shAddressType.p2wpkhInP2sh)
|
@override
|
||||||
return generateP2SHAddress(hd: hd, index: index, network: network);
|
BitcoinBaseAddress generateAddress({
|
||||||
|
required CWBitcoinDerivationType derivationType,
|
||||||
|
required bool isChange,
|
||||||
|
required int index,
|
||||||
|
required BitcoinAddressType addressType,
|
||||||
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
|
}) {
|
||||||
|
final hdWallet = hdWallets[derivationType]!;
|
||||||
|
|
||||||
return generateP2WPKHAddress(hd: hd, index: index, network: network);
|
if (derivationType == CWBitcoinDerivationType.old) {
|
||||||
|
final pub = hdWallet
|
||||||
|
.childKey(Bip32KeyIndex(isChange ? 1 : 0))
|
||||||
|
.childKey(Bip32KeyIndex(index))
|
||||||
|
.publicKey;
|
||||||
|
|
||||||
|
switch (addressType) {
|
||||||
|
case P2pkhAddressType.p2pkh:
|
||||||
|
return ECPublic.fromBip32(pub).toP2pkhAddress();
|
||||||
|
case SegwitAddresType.p2tr:
|
||||||
|
return ECPublic.fromBip32(pub).toP2trAddress();
|
||||||
|
case SegwitAddresType.p2wsh:
|
||||||
|
return ECPublic.fromBip32(pub).toP2wshAddress();
|
||||||
|
case P2shAddressType.p2wpkhInP2sh:
|
||||||
|
return ECPublic.fromBip32(pub).toP2wpkhInP2sh();
|
||||||
|
case SegwitAddresType.p2wpkh:
|
||||||
|
return ECPublic.fromBip32(pub).toP2wpkhAddress();
|
||||||
|
default:
|
||||||
|
throw ArgumentError('Invalid address type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (addressType) {
|
||||||
|
case P2pkhAddressType.p2pkh:
|
||||||
|
return P2pkhAddress.fromDerivation(
|
||||||
|
bip32: hdWallet,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
);
|
||||||
|
case SegwitAddresType.p2tr:
|
||||||
|
return P2trAddress.fromDerivation(
|
||||||
|
bip32: hdWallet,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
);
|
||||||
|
case SegwitAddresType.p2wsh:
|
||||||
|
return P2wshAddress.fromDerivation(
|
||||||
|
bip32: hdWallet,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
);
|
||||||
|
case P2shAddressType.p2wpkhInP2sh:
|
||||||
|
return P2shAddress.fromDerivation(
|
||||||
|
bip32: hdWallet,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
type: P2shAddressType.p2wpkhInP2sh,
|
||||||
|
);
|
||||||
|
case SegwitAddresType.p2wpkh:
|
||||||
|
return P2wpkhAddress.fromDerivation(
|
||||||
|
bip32: hdWallet,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw ArgumentError('Invalid address type');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ class BitcoinNewWalletCredentials extends WalletCredentials {
|
||||||
required String name,
|
required String name,
|
||||||
WalletInfo? walletInfo,
|
WalletInfo? walletInfo,
|
||||||
String? password,
|
String? password,
|
||||||
DerivationType? derivationType,
|
|
||||||
String? derivationPath,
|
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
this.mnemonic,
|
this.mnemonic,
|
||||||
String? parentAddress,
|
String? parentAddress,
|
||||||
|
@ -28,19 +26,15 @@ class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
required String name,
|
required String name,
|
||||||
required String password,
|
required String password,
|
||||||
required this.mnemonic,
|
required this.mnemonic,
|
||||||
|
required super.derivations,
|
||||||
WalletInfo? walletInfo,
|
WalletInfo? walletInfo,
|
||||||
required DerivationType derivationType,
|
|
||||||
required String derivationPath,
|
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
}) : super(
|
}) : super(
|
||||||
name: name,
|
name: name,
|
||||||
password: password,
|
password: password,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
derivationInfo: DerivationInfo(
|
);
|
||||||
derivationType: derivationType,
|
|
||||||
derivationPath: derivationPath,
|
|
||||||
));
|
|
||||||
|
|
||||||
final String mnemonic;
|
final String mnemonic;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:io';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
||||||
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
@ -14,18 +13,24 @@ import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
|
||||||
|
|
||||||
class BitcoinWalletService extends WalletService<
|
class BitcoinWalletService extends WalletService<
|
||||||
BitcoinNewWalletCredentials,
|
BitcoinNewWalletCredentials,
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
BitcoinRestoreWalletFromSeedCredentials,
|
||||||
BitcoinRestoreWalletFromWIFCredentials,
|
BitcoinRestoreWalletFromWIFCredentials,
|
||||||
BitcoinRestoreWalletFromHardware> {
|
BitcoinRestoreWalletFromHardware> {
|
||||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
|
BitcoinWalletService(
|
||||||
|
this.walletInfoSource,
|
||||||
|
this.unspentCoinsInfoSource,
|
||||||
|
this.alwaysScan,
|
||||||
|
this.isDirect,
|
||||||
|
this.mempoolAPIEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
final bool alwaysScan;
|
final bool alwaysScan;
|
||||||
|
final bool mempoolAPIEnabled;
|
||||||
final bool isDirect;
|
final bool isDirect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -37,7 +42,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
credentials.walletInfo?.network = network.value;
|
credentials.walletInfo?.network = network.value;
|
||||||
|
|
||||||
final String mnemonic;
|
final String mnemonic;
|
||||||
switch ( credentials.walletInfo?.derivationInfo?.derivationType) {
|
switch (credentials.walletInfo?.derivationInfo?.derivationType) {
|
||||||
case DerivationType.bip39:
|
case DerivationType.bip39:
|
||||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||||
|
|
||||||
|
@ -57,6 +62,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
network: network,
|
network: network,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
|
@ -80,6 +86,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -93,6 +100,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -118,6 +126,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: currentWalletInfo,
|
walletInfo: currentWalletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -146,6 +155,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -160,10 +170,6 @@ class BitcoinWalletService extends WalletService<
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
||||||
{bool? isTestnet}) async {
|
{bool? isTestnet}) async {
|
||||||
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
|
|
||||||
throw BitcoinMnemonicIsIncorrectException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
||||||
credentials.walletInfo?.network = network.value;
|
credentials.walletInfo?.network = network.value;
|
||||||
|
|
||||||
|
@ -175,8 +181,8 @@ class BitcoinWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
network: network,
|
network: network,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.save();
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,9 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
enum ConnectionStatus { connected, disconnected, connecting, failed }
|
|
||||||
|
|
||||||
String jsonrpcparams(List<Object> params) {
|
|
||||||
final _params = params.map((val) => '"${val.toString()}"').join(',');
|
|
||||||
return '[$_params]';
|
|
||||||
}
|
|
||||||
|
|
||||||
String jsonrpc(
|
String jsonrpc(
|
||||||
{required String method,
|
{required String method,
|
||||||
required List<Object> params,
|
required List<Object> params,
|
||||||
|
@ -317,13 +309,38 @@ class ElectrumClient {
|
||||||
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
||||||
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
|
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
|
||||||
|
|
||||||
BehaviorSubject<Object>? tweaksSubscribe({required int height, required int count}) {
|
BehaviorSubject<Object>? tweaksSubscribe({required int height, required int count}) =>
|
||||||
return subscribe<Object>(
|
subscribe<Object>(
|
||||||
id: 'blockchain.tweaks.subscribe',
|
id: 'blockchain.tweaks.subscribe',
|
||||||
method: 'blockchain.tweaks.subscribe',
|
method: 'blockchain.tweaks.subscribe',
|
||||||
params: [height, count, false],
|
params: [height, count, true],
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
Future<dynamic> tweaksRegister({
|
||||||
|
required String secViewKey,
|
||||||
|
required String pubSpendKey,
|
||||||
|
List<int> labels = const [],
|
||||||
|
}) =>
|
||||||
|
call(
|
||||||
|
method: 'blockchain.tweaks.register',
|
||||||
|
params: [secViewKey, pubSpendKey, labels],
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<dynamic> tweaksErase({required String pubSpendKey}) => call(
|
||||||
|
method: 'blockchain.tweaks.erase',
|
||||||
|
params: [pubSpendKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
BehaviorSubject<Object>? tweaksScan({required String pubSpendKey}) => subscribe<Object>(
|
||||||
|
id: 'blockchain.tweaks.scan',
|
||||||
|
method: 'blockchain.tweaks.scan',
|
||||||
|
params: [pubSpendKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<dynamic> tweaksGet({required String pubSpendKey}) => call(
|
||||||
|
method: 'blockchain.tweaks.get',
|
||||||
|
params: [pubSpendKey],
|
||||||
|
);
|
||||||
|
|
||||||
Future<dynamic> getTweaks({required int height}) async =>
|
Future<dynamic> getTweaks({required int height}) async =>
|
||||||
await callWithTimeout(method: 'blockchain.tweaks.subscribe', params: [height, 1, false]);
|
await callWithTimeout(method: 'blockchain.tweaks.subscribe', params: [height, 1, false]);
|
||||||
|
@ -369,20 +386,20 @@ class ElectrumClient {
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
|
// Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
|
||||||
try {
|
// try {
|
||||||
final topDoubleString = await estimatefee(p: 1);
|
// final topDoubleString = await estimatefee(p: 1);
|
||||||
final middleDoubleString = await estimatefee(p: 5);
|
// final middleDoubleString = await estimatefee(p: 5);
|
||||||
final bottomDoubleString = await estimatefee(p: 10);
|
// final bottomDoubleString = await estimatefee(p: 10);
|
||||||
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
|
// final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
|
||||||
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
// final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
||||||
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
// final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
||||||
|
|
||||||
return [bottom, middle, top];
|
// return [bottom, middle, top];
|
||||||
} catch (_) {
|
// } catch (_) {
|
||||||
return [];
|
// return [];
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe
|
// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe
|
||||||
// example response:
|
// example response:
|
||||||
|
@ -527,6 +544,7 @@ class ElectrumClient {
|
||||||
_tasks[method]?.subject?.add(params.last);
|
_tasks[method]?.subject?.add(params.last);
|
||||||
break;
|
break;
|
||||||
case 'blockchain.tweaks.subscribe':
|
case 'blockchain.tweaks.subscribe':
|
||||||
|
case 'blockchain.tweaks.scan':
|
||||||
final params = request['params'] as List<dynamic>;
|
final params = request['params'] as List<dynamic>;
|
||||||
_tasks[_tasks.keys.first]?.subject?.add(params.last);
|
_tasks[_tasks.keys.first]?.subject?.add(params.last);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
import 'package:cw_core/balance.dart';
|
||||||
|
|
||||||
class ElectrumBalance extends Balance {
|
class ElectrumBalance extends Balance {
|
||||||
|
@ -31,32 +31,35 @@ class ElectrumBalance extends Balance {
|
||||||
|
|
||||||
int confirmed;
|
int confirmed;
|
||||||
int unconfirmed;
|
int unconfirmed;
|
||||||
final int frozen;
|
int frozen;
|
||||||
int secondConfirmed = 0;
|
int secondConfirmed = 0;
|
||||||
int secondUnconfirmed = 0;
|
int secondUnconfirmed = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedAvailableBalance =>
|
String get formattedAvailableBalance =>
|
||||||
bitcoinAmountToString(amount: confirmed - frozen);
|
BitcoinAmountUtils.bitcoinAmountToString(amount: confirmed - frozen);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
|
String get formattedAdditionalBalance =>
|
||||||
|
BitcoinAmountUtils.bitcoinAmountToString(amount: unconfirmed);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedUnAvailableBalance {
|
String get formattedUnAvailableBalance {
|
||||||
final frozenFormatted = bitcoinAmountToString(amount: frozen);
|
final frozenFormatted = BitcoinAmountUtils.bitcoinAmountToString(amount: frozen);
|
||||||
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedSecondAvailableBalance => bitcoinAmountToString(amount: secondConfirmed);
|
String get formattedSecondAvailableBalance =>
|
||||||
|
BitcoinAmountUtils.bitcoinAmountToString(amount: secondConfirmed);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedSecondAdditionalBalance => bitcoinAmountToString(amount: secondUnconfirmed);
|
String get formattedSecondAdditionalBalance =>
|
||||||
|
BitcoinAmountUtils.bitcoinAmountToString(amount: secondUnconfirmed);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedFullAvailableBalance =>
|
String get formattedFullAvailableBalance =>
|
||||||
bitcoinAmountToString(amount: confirmed + secondConfirmed - frozen);
|
BitcoinAmountUtils.bitcoinAmountToString(amount: confirmed + secondConfirmed - frozen);
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'confirmed': confirmed,
|
'confirmed': confirmed,
|
||||||
|
|
|
@ -4,11 +4,8 @@ import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/transaction_history.dart';
|
import 'package:cw_core/transaction_history.dart';
|
||||||
import 'package:cw_core/utils/file.dart';
|
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_core/transaction_history.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
|
||||||
|
|
||||||
part 'electrum_transaction_history.g.dart';
|
part 'electrum_transaction_history.g.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/address_from_output.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
@ -12,17 +10,39 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:hex/hex.dart';
|
import 'package:hex/hex.dart';
|
||||||
|
|
||||||
class ElectrumTransactionBundle {
|
class ElectrumTransactionBundle {
|
||||||
ElectrumTransactionBundle(this.originalTransaction,
|
ElectrumTransactionBundle(
|
||||||
{required this.ins, required this.confirmations, this.time});
|
this.originalTransaction, {
|
||||||
|
required this.ins,
|
||||||
|
required this.confirmations,
|
||||||
|
this.time,
|
||||||
|
});
|
||||||
|
|
||||||
final BtcTransaction originalTransaction;
|
final BtcTransaction originalTransaction;
|
||||||
final List<BtcTransaction> ins;
|
final List<BtcTransaction> ins;
|
||||||
final int? time;
|
final int? time;
|
||||||
final int confirmations;
|
final int confirmations;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'originalTransaction': originalTransaction.toHex(),
|
||||||
|
'ins': ins.map((e) => e.toHex()).toList(),
|
||||||
|
'confirmations': confirmations,
|
||||||
|
'time': time,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static ElectrumTransactionBundle fromJson(Map<String, dynamic> data) {
|
||||||
|
return ElectrumTransactionBundle(
|
||||||
|
BtcTransaction.fromRaw(data['originalTransaction'] as String),
|
||||||
|
ins: (data['ins'] as List<Object>).map((e) => BtcTransaction.fromRaw(e as String)).toList(),
|
||||||
|
confirmations: data['confirmations'] as int,
|
||||||
|
time: data['time'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElectrumTransactionInfo extends TransactionInfo {
|
class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
List<BitcoinSilentPaymentsUnspent>? unspents;
|
List<BitcoinUnspent>? unspents;
|
||||||
bool isReceivedSilentPayment;
|
bool isReceivedSilentPayment;
|
||||||
|
|
||||||
ElectrumTransactionInfo(
|
ElectrumTransactionInfo(
|
||||||
|
@ -75,7 +95,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
final vout = vin['vout'] as int;
|
final vout = vin['vout'] as int;
|
||||||
final out = vin['tx']['vout'][vout] as Map;
|
final out = vin['tx']['vout'][vout] as Map;
|
||||||
final outAddresses = (out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
|
final outAddresses = (out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
|
||||||
inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
|
inputsAmount +=
|
||||||
|
BitcoinAmountUtils.stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
|
||||||
|
|
||||||
if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) {
|
if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) {
|
||||||
direction = TransactionDirection.outgoing;
|
direction = TransactionDirection.outgoing;
|
||||||
|
@ -85,7 +106,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
for (dynamic out in vout) {
|
for (dynamic out in vout) {
|
||||||
final outAddresses = out['scriptPubKey']['addresses'] as List<Object>? ?? [];
|
final outAddresses = out['scriptPubKey']['addresses'] as List<Object>? ?? [];
|
||||||
final ntrs = outAddresses.toSet().intersection(addressesSet);
|
final ntrs = outAddresses.toSet().intersection(addressesSet);
|
||||||
final value = stringDoubleToBitcoinAmount((out['value'] as double? ?? 0.0).toString());
|
final value = BitcoinAmountUtils.stringDoubleToBitcoinAmount(
|
||||||
|
(out['value'] as double? ?? 0.0).toString());
|
||||||
totalOutAmount += value;
|
totalOutAmount += value;
|
||||||
|
|
||||||
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
|
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
|
||||||
|
@ -121,22 +143,33 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
List<String> inputAddresses = [];
|
List<String> inputAddresses = [];
|
||||||
List<String> outputAddresses = [];
|
List<String> outputAddresses = [];
|
||||||
|
|
||||||
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
try {
|
||||||
final input = bundle.originalTransaction.inputs[i];
|
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
||||||
final inputTransaction = bundle.ins[i];
|
final input = bundle.originalTransaction.inputs[i];
|
||||||
final outTransaction = inputTransaction.outputs[input.txIndex];
|
final inputTransaction = bundle.ins[i];
|
||||||
inputAmount += outTransaction.amount.toInt();
|
final outTransaction = inputTransaction.outputs[input.txIndex];
|
||||||
if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) {
|
inputAmount += outTransaction.amount.toInt();
|
||||||
direction = TransactionDirection.outgoing;
|
if (addresses.contains(
|
||||||
inputAddresses.add(addressFromOutputScript(outTransaction.scriptPubKey, network));
|
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network))) {
|
||||||
|
direction = TransactionDirection.outgoing;
|
||||||
|
inputAddresses.add(
|
||||||
|
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(bundle.originalTransaction.txId());
|
||||||
|
print("original: ${bundle.originalTransaction}");
|
||||||
|
print("bundle.inputs: ${bundle.originalTransaction.inputs}");
|
||||||
|
print("ins: ${bundle.ins}");
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
final receivedAmounts = <int>[];
|
final receivedAmounts = <int>[];
|
||||||
for (final out in bundle.originalTransaction.outputs) {
|
for (final out in bundle.originalTransaction.outputs) {
|
||||||
totalOutAmount += out.amount.toInt();
|
totalOutAmount += out.amount.toInt();
|
||||||
final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
|
final addressExists = addresses
|
||||||
final address = addressFromOutputScript(out.scriptPubKey, network);
|
.contains(BitcoinAddressUtils.addressFromOutputScript(out.scriptPubKey, network));
|
||||||
|
final address = BitcoinAddressUtils.addressFromOutputScript(out.scriptPubKey, network);
|
||||||
|
|
||||||
if (address.isNotEmpty) outputAddresses.add(address);
|
if (address.isNotEmpty) outputAddresses.add(address);
|
||||||
|
|
||||||
|
@ -208,8 +241,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(),
|
outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(),
|
||||||
to: data['to'] as String?,
|
to: data['to'] as String?,
|
||||||
unspents: unspents
|
unspents: unspents
|
||||||
.map((unspent) =>
|
.map((unspent) => BitcoinUnspent.fromJSON(null, unspent as Map<String, dynamic>))
|
||||||
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>))
|
|
||||||
.toList(),
|
.toList(),
|
||||||
isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false,
|
isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
@ -221,11 +253,11 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String amountFormatted() =>
|
String amountFormatted() =>
|
||||||
'${formatAmount(bitcoinAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}';
|
'${formatAmount(BitcoinAmountUtils.bitcoinAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? feeFormatted() => fee != null
|
String? feeFormatted() => fee != null
|
||||||
? '${formatAmount(bitcoinAmountToString(amount: fee!))} ${walletTypeToCryptoCurrency(type).title}'
|
? '${formatAmount(BitcoinAmountUtils.bitcoinAmountToString(amount: fee!))} ${walletTypeToCryptoCurrency(type).title}'
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,6 +11,8 @@ import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'electrum_wallet_addresses.g.dart';
|
part 'electrum_wallet_addresses.g.dart';
|
||||||
|
|
||||||
|
enum CWBitcoinDerivationType { old, electrum, bip39, mweb }
|
||||||
|
|
||||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
|
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
|
||||||
|
|
||||||
const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
|
const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
|
||||||
|
@ -33,8 +35,7 @@ const List<BitcoinAddressType> BITCOIN_CASH_ADDRESS_TYPES = [
|
||||||
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
ElectrumWalletAddressesBase(
|
ElectrumWalletAddressesBase(
|
||||||
WalletInfo walletInfo, {
|
WalletInfo walletInfo, {
|
||||||
required this.mainHd,
|
required this.hdWallets,
|
||||||
required this.sideHd,
|
|
||||||
required this.network,
|
required this.network,
|
||||||
required this.isHardwareWallet,
|
required this.isHardwareWallet,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
|
@ -43,18 +44,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||||
int initialSilentAddressIndex = 0,
|
int initialSilentAddressIndex = 0,
|
||||||
List<BitcoinAddressRecord>? initialMwebAddresses,
|
List<BitcoinAddressRecord>? initialMwebAddresses,
|
||||||
Bip32Slip10Secp256k1? masterHd,
|
|
||||||
BitcoinAddressType? initialAddressPageType,
|
BitcoinAddressType? initialAddressPageType,
|
||||||
|
}) : _allAddresses = ObservableList.of(initialAddresses ?? []),
|
||||||
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
|
||||||
addressesByReceiveType =
|
addressesByReceiveType =
|
||||||
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
||||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
|
||||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
(initialAddresses ?? []).where((addressRecord) => !addressRecord.isChange).toSet()),
|
||||||
.toSet()),
|
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
|
||||||
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
changeAddresses = ObservableList<BitcoinAddressRecord>.of(
|
||||||
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
(initialAddresses ?? []).where((addressRecord) => addressRecord.isChange).toSet()),
|
||||||
.toSet()),
|
|
||||||
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
|
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
|
||||||
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
|
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
|
||||||
_addressPageType = initialAddressPageType ??
|
_addressPageType = initialAddressPageType ??
|
||||||
|
@ -67,33 +65,23 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
mwebAddresses =
|
mwebAddresses =
|
||||||
ObservableList<BitcoinAddressRecord>.of((initialMwebAddresses ?? []).toSet()),
|
ObservableList<BitcoinAddressRecord>.of((initialMwebAddresses ?? []).toSet()),
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
if (masterHd != null) {
|
// TODO: initial silent address, not every time
|
||||||
silentAddress = SilentPaymentOwner.fromPrivateKeys(
|
silentAddress = SilentPaymentOwner.fromBip32(bip32);
|
||||||
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()),
|
|
||||||
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()),
|
|
||||||
network: network,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (silentAddresses.length == 0) {
|
if (silentAddresses.length == 0) {
|
||||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||||
silentAddress.toString(),
|
silentAddress.toString(),
|
||||||
index: 0,
|
labelIndex: 1,
|
||||||
isHidden: false,
|
name: "",
|
||||||
name: "",
|
type: SilentPaymentsAddresType.p2sp,
|
||||||
silentPaymentTweak: null,
|
));
|
||||||
network: network,
|
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||||
type: SilentPaymentsAddresType.p2sp,
|
silentAddress!.toLabeledSilentPaymentAddress(0).toString(),
|
||||||
));
|
name: "",
|
||||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
labelIndex: 0,
|
||||||
silentAddress!.toLabeledSilentPaymentAddress(0).toString(),
|
labelHex: BytesUtils.toHexString(silentAddress!.generateLabel(0)),
|
||||||
index: 0,
|
type: SilentPaymentsAddresType.p2sp,
|
||||||
isHidden: true,
|
));
|
||||||
name: "",
|
|
||||||
silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(0)),
|
|
||||||
network: network,
|
|
||||||
type: SilentPaymentsAddresType.p2sp,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAddressesByMatch();
|
updateAddressesByMatch();
|
||||||
|
@ -103,7 +91,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
static const defaultChangeAddressesCount = 17;
|
static const defaultChangeAddressesCount = 17;
|
||||||
static const gap = 20;
|
static const gap = 20;
|
||||||
|
|
||||||
final ObservableList<BitcoinAddressRecord> _addresses;
|
final ObservableList<BitcoinAddressRecord> _allAddresses;
|
||||||
final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
||||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||||
|
@ -112,8 +100,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
// TODO: add this variable in `litecoin_wallet_addresses` and just add a cast in cw_bitcoin to use it
|
// TODO: add this variable in `litecoin_wallet_addresses` and just add a cast in cw_bitcoin to use it
|
||||||
final ObservableList<BitcoinAddressRecord> mwebAddresses;
|
final ObservableList<BitcoinAddressRecord> mwebAddresses;
|
||||||
final BasedUtxoNetwork network;
|
final BasedUtxoNetwork network;
|
||||||
final Bip32Slip10Secp256k1 mainHd;
|
|
||||||
final Bip32Slip10Secp256k1 sideHd;
|
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets;
|
||||||
|
Bip32Slip10Secp256k1 get bip32 =>
|
||||||
|
hdWallets[CWBitcoinDerivationType.bip39] ?? hdWallets[CWBitcoinDerivationType.electrum]!;
|
||||||
|
|
||||||
final bool isHardwareWallet;
|
final bool isHardwareWallet;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -129,7 +120,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
String? activeSilentAddress;
|
String? activeSilentAddress;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<BitcoinAddressRecord> get allAddresses => _addresses;
|
List<BitcoinAddressRecord> get allAddresses => _allAddresses.toList();
|
||||||
|
|
||||||
|
@computed
|
||||||
|
Set<String> get allScriptHashes =>
|
||||||
|
_allAddresses.map((addressRecord) => addressRecord.scriptHash).toSet();
|
||||||
|
|
||||||
|
BitcoinAddressRecord getFromAddresses(String address) {
|
||||||
|
return _allAddresses.firstWhere((element) => element.address == address);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@computed
|
@computed
|
||||||
|
@ -174,31 +173,34 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||||
final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
late BitcoinSilentPaymentAddressRecord selected;
|
||||||
|
try {
|
||||||
|
selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
||||||
|
} catch (_) {
|
||||||
|
selected = silentAddresses[0];
|
||||||
|
}
|
||||||
|
|
||||||
if (selected.silentPaymentTweak != null && silentAddress != null) {
|
if (selected.labelHex != null && silentAddress != null) {
|
||||||
activeSilentAddress =
|
activeSilentAddress =
|
||||||
silentAddress!.toLabeledSilentPaymentAddress(selected.index).toString();
|
silentAddress!.toLabeledSilentPaymentAddress(selected.labelIndex).toString();
|
||||||
} else {
|
} else {
|
||||||
activeSilentAddress = silentAddress!.toString();
|
activeSilentAddress = silentAddress!.toString();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final addressRecord = _addresses.firstWhere(
|
final addressRecord = _allAddresses.firstWhere(
|
||||||
(addressRecord) => addressRecord.address == addr,
|
(addressRecord) => addressRecord.address == addr,
|
||||||
);
|
);
|
||||||
|
|
||||||
previousAddressRecord = addressRecord;
|
previousAddressRecord = addressRecord;
|
||||||
receiveAddresses.remove(addressRecord);
|
|
||||||
receiveAddresses.insert(0, addressRecord);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("ElectrumWalletAddressBase: set address ($addr): $e");
|
print("ElectrumWalletAddressBase: set address ($addr): $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get primaryAddress => getAddress(index: 0, hd: mainHd, addressType: addressPageType);
|
String get primaryAddress => _allAddresses.first.address;
|
||||||
|
|
||||||
Map<String, int> currentReceiveAddressIndexByType;
|
Map<String, int> currentReceiveAddressIndexByType;
|
||||||
|
|
||||||
|
@ -223,7 +225,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
int get totalCountOfReceiveAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
|
int get totalCountOfReceiveAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
|
||||||
if (!addressRecord.isHidden) {
|
if (!addressRecord.isChange) {
|
||||||
return acc + 1;
|
return acc + 1;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -231,7 +233,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
int get totalCountOfChangeAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
|
int get totalCountOfChangeAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
|
||||||
if (addressRecord.isHidden) {
|
if (addressRecord.isChange) {
|
||||||
return acc + 1;
|
return acc + 1;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -240,26 +242,25 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (walletInfo.type == WalletType.bitcoinCash) {
|
if (walletInfo.type == WalletType.bitcoinCash) {
|
||||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||||
} else if (walletInfo.type == WalletType.litecoin) {
|
} else if (walletInfo.type == WalletType.litecoin) {
|
||||||
await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
|
await generateInitialAddresses(type: SegwitAddresType.p2wpkh);
|
||||||
if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
|
if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
|
||||||
await _generateInitialAddresses(type: SegwitAddresType.mweb);
|
await generateInitialAddresses(type: SegwitAddresType.mweb);
|
||||||
}
|
}
|
||||||
} else if (walletInfo.type == WalletType.bitcoin) {
|
} else if (walletInfo.type == WalletType.bitcoin) {
|
||||||
await _generateInitialAddresses();
|
await generateInitialAddresses(type: SegwitAddresType.p2wpkh);
|
||||||
if (!isHardwareWallet) {
|
if (!isHardwareWallet) {
|
||||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||||
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
|
await generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
|
||||||
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
|
await generateInitialAddresses(type: SegwitAddresType.p2tr);
|
||||||
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
|
await generateInitialAddresses(type: SegwitAddresType.p2wsh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAddressesByMatch();
|
updateAddressesByMatch();
|
||||||
updateReceiveAddresses();
|
updateReceiveAddresses();
|
||||||
updateChangeAddresses();
|
updateChangeAddresses();
|
||||||
_validateAddresses();
|
|
||||||
await updateAddressesInBox();
|
await updateAddressesInBox();
|
||||||
|
|
||||||
if (currentReceiveAddressIndex >= receiveAddresses.length) {
|
if (currentReceiveAddressIndex >= receiveAddresses.length) {
|
||||||
|
@ -272,16 +273,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<BitcoinAddressRecord> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
|
Future<BitcoinAddressRecord> getChangeAddress({
|
||||||
|
List<BitcoinUnspent>? inputs,
|
||||||
|
List<BitcoinOutput>? outputs,
|
||||||
|
bool isPegIn = false,
|
||||||
|
}) async {
|
||||||
updateChangeAddresses();
|
updateChangeAddresses();
|
||||||
|
|
||||||
if (changeAddresses.isEmpty) {
|
|
||||||
final newAddresses = await _createNewAddresses(gap,
|
|
||||||
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
|
|
||||||
isHidden: true);
|
|
||||||
addAddresses(newAddresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentChangeAddressIndex >= changeAddresses.length) {
|
if (currentChangeAddressIndex >= changeAddresses.length) {
|
||||||
currentChangeAddressIndex = 0;
|
currentChangeAddressIndex = 0;
|
||||||
}
|
}
|
||||||
|
@ -297,7 +295,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
final labels = <String, String>{};
|
final labels = <String, String>{};
|
||||||
for (int i = 0; i < silentAddresses.length; i++) {
|
for (int i = 0; i < silentAddresses.length; i++) {
|
||||||
final silentAddressRecord = silentAddresses[i];
|
final silentAddressRecord = silentAddresses[i];
|
||||||
final silentPaymentTweak = silentAddressRecord.silentPaymentTweak;
|
final silentPaymentTweak = silentAddressRecord.labelHex;
|
||||||
|
|
||||||
if (silentPaymentTweak != null &&
|
if (silentPaymentTweak != null &&
|
||||||
SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) {
|
SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) {
|
||||||
|
@ -321,53 +319,87 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
final address = BitcoinSilentPaymentAddressRecord(
|
final address = BitcoinSilentPaymentAddressRecord(
|
||||||
silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(),
|
silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(),
|
||||||
index: currentSilentAddressIndex,
|
labelIndex: currentSilentAddressIndex,
|
||||||
isHidden: false,
|
|
||||||
name: label,
|
name: label,
|
||||||
silentPaymentTweak:
|
labelHex: BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)),
|
||||||
BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)),
|
|
||||||
network: network,
|
|
||||||
type: SilentPaymentsAddresType.p2sp,
|
type: SilentPaymentsAddresType.p2sp,
|
||||||
);
|
);
|
||||||
|
|
||||||
silentAddresses.add(address);
|
silentAddresses.add(address);
|
||||||
updateAddressesByMatch();
|
Future.delayed(Duration.zero, () => updateAddressesByMatch());
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newAddressIndex = addressesByReceiveType.fold(
|
final newAddressIndex = addressesByReceiveType.fold(
|
||||||
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
|
0, (int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc);
|
||||||
|
|
||||||
|
final derivationInfo = BitcoinAddressUtils.getDerivationFromType(addressPageType);
|
||||||
final address = BitcoinAddressRecord(
|
final address = BitcoinAddressRecord(
|
||||||
getAddress(index: newAddressIndex, hd: mainHd, addressType: addressPageType),
|
getAddress(
|
||||||
|
derivationType: CWBitcoinDerivationType.bip39,
|
||||||
|
isChange: false,
|
||||||
|
index: newAddressIndex,
|
||||||
|
addressType: addressPageType,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
),
|
||||||
index: newAddressIndex,
|
index: newAddressIndex,
|
||||||
isHidden: false,
|
isChange: false,
|
||||||
name: label,
|
name: label,
|
||||||
type: addressPageType,
|
type: addressPageType,
|
||||||
network: network,
|
network: network,
|
||||||
|
derivationInfo: BitcoinAddressUtils.getDerivationFromType(addressPageType),
|
||||||
|
derivationType: CWBitcoinDerivationType.bip39,
|
||||||
);
|
);
|
||||||
_addresses.add(address);
|
_allAddresses.add(address);
|
||||||
updateAddressesByMatch();
|
Future.delayed(Duration.zero, () => updateAddressesByMatch());
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getAddress({
|
BitcoinBaseAddress generateAddress({
|
||||||
|
required CWBitcoinDerivationType derivationType,
|
||||||
|
required bool isChange,
|
||||||
required int index,
|
required int index,
|
||||||
required Bip32Slip10Secp256k1 hd,
|
required BitcoinAddressType addressType,
|
||||||
BitcoinAddressType? addressType,
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
}) =>
|
}) {
|
||||||
'';
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getAddress({
|
||||||
|
required CWBitcoinDerivationType derivationType,
|
||||||
|
required bool isChange,
|
||||||
|
required int index,
|
||||||
|
required BitcoinAddressType addressType,
|
||||||
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
|
}) {
|
||||||
|
return generateAddress(
|
||||||
|
derivationType: derivationType,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
addressType: addressType,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
).toAddress(network);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> getAddressAsync({
|
Future<String> getAddressAsync({
|
||||||
|
required CWBitcoinDerivationType derivationType,
|
||||||
|
required bool isChange,
|
||||||
required int index,
|
required int index,
|
||||||
required Bip32Slip10Secp256k1 hd,
|
required BitcoinAddressType addressType,
|
||||||
BitcoinAddressType? addressType,
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
}) async =>
|
}) async =>
|
||||||
getAddress(index: index, hd: hd, addressType: addressType);
|
getAddress(
|
||||||
|
derivationType: derivationType,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
addressType: addressType,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
);
|
||||||
|
|
||||||
|
@action
|
||||||
void addBitcoinAddressTypes() {
|
void addBitcoinAddressTypes() {
|
||||||
final lastP2wpkh = _addresses
|
final lastP2wpkh = _allAddresses
|
||||||
.where((addressRecord) =>
|
.where((addressRecord) =>
|
||||||
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
||||||
.toList()
|
.toList()
|
||||||
|
@ -378,7 +410,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
addressesMap[address] = 'Active - P2WPKH';
|
addressesMap[address] = 'Active - P2WPKH';
|
||||||
}
|
}
|
||||||
|
|
||||||
final lastP2pkh = _addresses.firstWhere(
|
final lastP2pkh = _allAddresses.firstWhere(
|
||||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
||||||
if (lastP2pkh.address != address) {
|
if (lastP2pkh.address != address) {
|
||||||
addressesMap[lastP2pkh.address] = 'P2PKH';
|
addressesMap[lastP2pkh.address] = 'P2PKH';
|
||||||
|
@ -386,7 +418,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
addressesMap[address] = 'Active - P2PKH';
|
addressesMap[address] = 'Active - P2PKH';
|
||||||
}
|
}
|
||||||
|
|
||||||
final lastP2sh = _addresses.firstWhere((addressRecord) =>
|
final lastP2sh = _allAddresses.firstWhere((addressRecord) =>
|
||||||
_isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh));
|
_isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh));
|
||||||
if (lastP2sh.address != address) {
|
if (lastP2sh.address != address) {
|
||||||
addressesMap[lastP2sh.address] = 'P2SH';
|
addressesMap[lastP2sh.address] = 'P2SH';
|
||||||
|
@ -394,7 +426,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
addressesMap[address] = 'Active - P2SH';
|
addressesMap[address] = 'Active - P2SH';
|
||||||
}
|
}
|
||||||
|
|
||||||
final lastP2tr = _addresses.firstWhere(
|
final lastP2tr = _allAddresses.firstWhere(
|
||||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
|
||||||
if (lastP2tr.address != address) {
|
if (lastP2tr.address != address) {
|
||||||
addressesMap[lastP2tr.address] = 'P2TR';
|
addressesMap[lastP2tr.address] = 'P2TR';
|
||||||
|
@ -402,7 +434,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
addressesMap[address] = 'Active - P2TR';
|
addressesMap[address] = 'Active - P2TR';
|
||||||
}
|
}
|
||||||
|
|
||||||
final lastP2wsh = _addresses.firstWhere(
|
final lastP2wsh = _allAddresses.firstWhere(
|
||||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
|
||||||
if (lastP2wsh.address != address) {
|
if (lastP2wsh.address != address) {
|
||||||
addressesMap[lastP2wsh.address] = 'P2WSH';
|
addressesMap[lastP2wsh.address] = 'P2WSH';
|
||||||
|
@ -411,7 +443,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
silentAddresses.forEach((addressRecord) {
|
silentAddresses.forEach((addressRecord) {
|
||||||
if (addressRecord.type != SilentPaymentsAddresType.p2sp || addressRecord.isHidden) {
|
if (addressRecord.type != SilentPaymentsAddresType.p2sp || addressRecord.isChange) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,8 +457,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
void addLitecoinAddressTypes() {
|
void addLitecoinAddressTypes() {
|
||||||
final lastP2wpkh = _addresses
|
final lastP2wpkh = _allAddresses
|
||||||
.where((addressRecord) =>
|
.where((addressRecord) =>
|
||||||
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
||||||
.toList()
|
.toList()
|
||||||
|
@ -437,7 +470,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
addressesMap[address] = 'Active - P2WPKH';
|
addressesMap[address] = 'Active - P2WPKH';
|
||||||
}
|
}
|
||||||
|
|
||||||
final lastMweb = _addresses.firstWhere(
|
final lastMweb = _allAddresses.firstWhere(
|
||||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb));
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb));
|
||||||
if (lastMweb.address != address) {
|
if (lastMweb.address != address) {
|
||||||
addressesMap[lastMweb.address] = 'MWEB';
|
addressesMap[lastMweb.address] = 'MWEB';
|
||||||
|
@ -446,8 +479,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
void addBitcoinCashAddressTypes() {
|
void addBitcoinCashAddressTypes() {
|
||||||
final lastP2pkh = _addresses.firstWhere(
|
final lastP2pkh = _allAddresses.firstWhere(
|
||||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
||||||
if (lastP2pkh.address != address) {
|
if (lastP2pkh.address != address) {
|
||||||
addressesMap[lastP2pkh.address] = 'P2PKH';
|
addressesMap[lastP2pkh.address] = 'P2PKH';
|
||||||
|
@ -457,13 +491,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@action
|
||||||
Future<void> updateAddressesInBox() async {
|
Future<void> updateAddressesInBox() async {
|
||||||
try {
|
try {
|
||||||
addressesMap.clear();
|
addressesMap.clear();
|
||||||
addressesMap[address] = 'Active';
|
addressesMap[address] = 'Active';
|
||||||
|
|
||||||
allAddressesMap.clear();
|
allAddressesMap.clear();
|
||||||
_addresses.forEach((addressRecord) {
|
_allAddresses.forEach((addressRecord) {
|
||||||
allAddressesMap[addressRecord.address] = addressRecord.name;
|
allAddressesMap[addressRecord.address] = addressRecord.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -490,7 +525,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@action
|
@action
|
||||||
void updateAddress(String address, String label) {
|
void updateAddress(String address, String label) {
|
||||||
BaseBitcoinAddressRecord? foundAddress;
|
BaseBitcoinAddressRecord? foundAddress;
|
||||||
_addresses.forEach((addressRecord) {
|
_allAddresses.forEach((addressRecord) {
|
||||||
if (addressRecord.address == address) {
|
if (addressRecord.address == address) {
|
||||||
foundAddress = addressRecord;
|
foundAddress = addressRecord;
|
||||||
}
|
}
|
||||||
|
@ -509,11 +544,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
if (foundAddress != null) {
|
if (foundAddress != null) {
|
||||||
foundAddress!.setNewName(label);
|
foundAddress!.setNewName(label);
|
||||||
|
|
||||||
if (foundAddress is BitcoinAddressRecord) {
|
if (foundAddress is! BitcoinAddressRecord) {
|
||||||
final index = _addresses.indexOf(foundAddress);
|
|
||||||
_addresses.remove(foundAddress);
|
|
||||||
_addresses.insert(index, foundAddress as BitcoinAddressRecord);
|
|
||||||
} else {
|
|
||||||
final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord);
|
final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord);
|
||||||
silentAddresses.remove(foundAddress);
|
silentAddresses.remove(foundAddress);
|
||||||
silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord);
|
silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord);
|
||||||
|
@ -530,86 +561,103 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
addressesByReceiveType.clear();
|
addressesByReceiveType.clear();
|
||||||
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
|
addressesByReceiveType.addAll(_allAddresses.where(_isAddressPageTypeMatch).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void updateReceiveAddresses() {
|
void updateReceiveAddresses() {
|
||||||
receiveAddresses.removeRange(0, receiveAddresses.length);
|
receiveAddresses.removeRange(0, receiveAddresses.length);
|
||||||
final newAddresses =
|
final newAddresses = _allAddresses.where((addressRecord) => !addressRecord.isChange);
|
||||||
_addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
|
||||||
receiveAddresses.addAll(newAddresses);
|
receiveAddresses.addAll(newAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void updateChangeAddresses() {
|
void updateChangeAddresses() {
|
||||||
changeAddresses.removeRange(0, changeAddresses.length);
|
changeAddresses.removeRange(0, changeAddresses.length);
|
||||||
final newAddresses = _addresses.where((addressRecord) =>
|
final newAddresses = _allAddresses.where((addressRecord) =>
|
||||||
addressRecord.isHidden &&
|
addressRecord.isChange &&
|
||||||
!addressRecord.isUsed &&
|
|
||||||
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
|
|
||||||
(walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh));
|
(walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh));
|
||||||
changeAddresses.addAll(newAddresses);
|
changeAddresses.addAll(newAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
|
Future<List<BitcoinAddressRecord>> discoverAddresses({
|
||||||
Future<String?> Function(BitcoinAddressRecord) getAddressHistory,
|
required CWBitcoinDerivationType derivationType,
|
||||||
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
|
required bool isChange,
|
||||||
final newAddresses = await _createNewAddresses(gap,
|
required BitcoinAddressType type,
|
||||||
startIndex: addressList.length, isHidden: isHidden, type: type);
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
|
}) async {
|
||||||
|
final gap = (isChange
|
||||||
|
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
|
||||||
|
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
|
||||||
|
|
||||||
|
final newAddresses = await _createNewAddresses(
|
||||||
|
derivationType: derivationType,
|
||||||
|
gap,
|
||||||
|
isChange: isChange,
|
||||||
|
type: type,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
);
|
||||||
addAddresses(newAddresses);
|
addAddresses(newAddresses);
|
||||||
|
return newAddresses;
|
||||||
final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
|
|
||||||
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
|
|
||||||
|
|
||||||
if (isLastAddressUsed) {
|
|
||||||
discoverAddresses(addressList, isHidden, getAddressHistory, type: type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _generateInitialAddresses(
|
@action
|
||||||
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
|
Future<void> generateInitialAddresses({required BitcoinAddressType type}) async {
|
||||||
var countOfReceiveAddresses = 0;
|
for (final derivationType in hdWallets.keys) {
|
||||||
var countOfHiddenAddresses = 0;
|
if (derivationType == CWBitcoinDerivationType.old && type == SegwitAddresType.p2wpkh) {
|
||||||
|
continue;
|
||||||
_addresses.forEach((addr) {
|
|
||||||
if (addr.type == type) {
|
|
||||||
if (addr.isHidden) {
|
|
||||||
countOfHiddenAddresses += 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
countOfReceiveAddresses += 1;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
|
final derivationInfo = BitcoinAddressUtils.getDerivationFromType(
|
||||||
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
|
type,
|
||||||
final newAddresses = await _createNewAddresses(addressesCount,
|
isElectrum: derivationType == CWBitcoinDerivationType.electrum,
|
||||||
startIndex: countOfReceiveAddresses, isHidden: false, type: type);
|
);
|
||||||
addAddresses(newAddresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
|
await discoverAddresses(
|
||||||
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
|
derivationType: derivationType,
|
||||||
final newAddresses = await _createNewAddresses(addressesCount,
|
isChange: false,
|
||||||
startIndex: countOfHiddenAddresses, isHidden: true, type: type);
|
type: type,
|
||||||
addAddresses(newAddresses);
|
derivationInfo: derivationInfo,
|
||||||
|
);
|
||||||
|
await discoverAddresses(
|
||||||
|
derivationType: derivationType,
|
||||||
|
isChange: true,
|
||||||
|
type: type,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
|
@action
|
||||||
{int startIndex = 0, bool isHidden = false, BitcoinAddressType? type}) async {
|
Future<List<BitcoinAddressRecord>> _createNewAddresses(
|
||||||
|
int count, {
|
||||||
|
required CWBitcoinDerivationType derivationType,
|
||||||
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
|
bool isChange = false,
|
||||||
|
BitcoinAddressType? type,
|
||||||
|
}) async {
|
||||||
final list = <BitcoinAddressRecord>[];
|
final list = <BitcoinAddressRecord>[];
|
||||||
|
final startIndex = (isChange ? receiveAddresses : changeAddresses)
|
||||||
|
.where((addr) => addr.derivationType == derivationType && addr.type == type)
|
||||||
|
.length;
|
||||||
|
|
||||||
for (var i = startIndex; i < count + startIndex; i++) {
|
for (var i = startIndex; i < count + startIndex; i++) {
|
||||||
final address = BitcoinAddressRecord(
|
final address = BitcoinAddressRecord(
|
||||||
await getAddressAsync(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
|
await getAddressAsync(
|
||||||
|
derivationType: derivationType,
|
||||||
|
isChange: isChange,
|
||||||
|
index: i,
|
||||||
|
addressType: type ?? addressPageType,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
),
|
||||||
index: i,
|
index: i,
|
||||||
isHidden: isHidden,
|
isChange: isChange,
|
||||||
|
isHidden: derivationType == CWBitcoinDerivationType.old && type != SegwitAddresType.p2wpkh,
|
||||||
type: type ?? addressPageType,
|
type: type ?? addressPageType,
|
||||||
network: network,
|
network: network,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
derivationType: derivationType,
|
||||||
);
|
);
|
||||||
list.add(address);
|
list.add(address);
|
||||||
}
|
}
|
||||||
|
@ -617,13 +665,28 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void updateAdresses(Iterable<BitcoinAddressRecord> addresses) {
|
||||||
|
for (final address in addresses) {
|
||||||
|
final index = _allAddresses.indexWhere((element) => element.address == address.address);
|
||||||
|
_allAddresses.replaceRange(index, index + 1, [address]);
|
||||||
|
|
||||||
|
updateAddressesByMatch();
|
||||||
|
updateReceiveAddresses();
|
||||||
|
updateChangeAddresses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
||||||
final addressesSet = this._addresses.toSet();
|
this._allAddresses.addAll(addresses);
|
||||||
addressesSet.addAll(addresses);
|
|
||||||
this._addresses.clear();
|
|
||||||
this._addresses.addAll(addressesSet);
|
|
||||||
updateAddressesByMatch();
|
updateAddressesByMatch();
|
||||||
|
updateReceiveAddresses();
|
||||||
|
updateChangeAddresses();
|
||||||
|
|
||||||
|
this.hiddenAddresses.addAll(addresses
|
||||||
|
.where((addressRecord) => addressRecord.isHidden)
|
||||||
|
.map((addressRecord) => addressRecord.address));
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -644,24 +707,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
updateAddressesByMatch();
|
updateAddressesByMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _validateAddresses() {
|
|
||||||
_addresses.forEach((element) async {
|
|
||||||
if (element.type == SegwitAddresType.mweb) {
|
|
||||||
// this would add a ton of startup lag for mweb addresses since we have 1000 of them
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!element.isHidden &&
|
|
||||||
element.address !=
|
|
||||||
await getAddressAsync(index: element.index, hd: mainHd, addressType: element.type)) {
|
|
||||||
element.isHidden = true;
|
|
||||||
} else if (element.isHidden &&
|
|
||||||
element.address !=
|
|
||||||
await getAddressAsync(index: element.index, hd: sideHd, addressType: element.type)) {
|
|
||||||
element.isHidden = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> setAddressType(BitcoinAddressType type) async {
|
Future<void> setAddressType(BitcoinAddressType type) async {
|
||||||
_addressPageType = type;
|
_addressPageType = type;
|
||||||
|
@ -674,12 +719,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return _isAddressByType(addressRecord, addressPageType);
|
return _isAddressByType(addressRecord, addressPageType);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
|
||||||
|
|
||||||
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
||||||
|
|
||||||
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
|
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) {
|
||||||
!addr.isHidden && !addr.isUsed && addr.type == type;
|
return !addr.isChange && !addr.isUsed && addr.type == type;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void deleteSilentPaymentAddress(String address) {
|
void deleteSilentPaymentAddress(String address) {
|
||||||
|
|
|
@ -3,10 +3,8 @@ import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/utils/file.dart';
|
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
class ElectrumWalletSnapshot {
|
class ElectrumWalletSnapshot {
|
||||||
|
@ -68,7 +66,7 @@ class ElectrumWalletSnapshot {
|
||||||
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
||||||
final addresses = addressesTmp
|
final addresses = addressesTmp
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
|
.map((addr) => BitcoinAddressRecord.fromJSON(addr))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final silentAddressesTmp = data['silent_addresses'] as List? ?? <Object>[];
|
final silentAddressesTmp = data['silent_addresses'] as List? ?? <Object>[];
|
||||||
|
@ -80,7 +78,7 @@ class ElectrumWalletSnapshot {
|
||||||
final mwebAddressTmp = data['mweb_addresses'] as List? ?? <Object>[];
|
final mwebAddressTmp = data['mweb_addresses'] as List? ?? <Object>[];
|
||||||
final mwebAddresses = mwebAddressTmp
|
final mwebAddresses = mwebAddressTmp
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
|
.map((addr) => BitcoinAddressRecord.fromJSON(addr))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final alwaysScan = data['alwaysScan'] as bool? ?? false;
|
final alwaysScan = data['alwaysScan'] as bool? ?? false;
|
||||||
|
@ -93,7 +91,7 @@ class ElectrumWalletSnapshot {
|
||||||
|
|
||||||
final derivationType = DerivationType
|
final derivationType = DerivationType
|
||||||
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
|
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
|
||||||
final derivationPath = data['derivationPath'] as String? ?? electrum_path;
|
final derivationPath = data['derivationPath'] as String? ?? ELECTRUM_PATH;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
regularAddressIndexByType = {
|
regularAddressIndexByType = {
|
||||||
|
|
794
cw_bitcoin/lib/electrum_worker/electrum_worker.dart
Normal file
794
cw_bitcoin/lib/electrum_worker/electrum_worker.dart
Normal file
|
@ -0,0 +1,794 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
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_transaction_priority.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
|
import 'package:cw_core/get_height_by_date.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
|
||||||
|
import 'package:cw_core/sync_status.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:sp_scanner/sp_scanner.dart';
|
||||||
|
|
||||||
|
class ElectrumWorker {
|
||||||
|
final SendPort sendPort;
|
||||||
|
ElectrumApiProvider? _electrumClient;
|
||||||
|
BasedUtxoNetwork? _network;
|
||||||
|
|
||||||
|
ElectrumWorker._(this.sendPort, {ElectrumApiProvider? electrumClient})
|
||||||
|
: _electrumClient = electrumClient;
|
||||||
|
|
||||||
|
static void run(SendPort sendPort) {
|
||||||
|
final worker = ElectrumWorker._(sendPort);
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
|
||||||
|
sendPort.send(receivePort.sendPort);
|
||||||
|
|
||||||
|
receivePort.listen(worker.handleMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendResponse<T, U>(ElectrumWorkerResponse<T, U> response) {
|
||||||
|
sendPort.send(jsonEncode(response.toJson()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendError(ElectrumWorkerErrorResponse response) {
|
||||||
|
sendPort.send(jsonEncode(response.toJson()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleMessage(dynamic message) async {
|
||||||
|
print("Worker: received message: $message");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, dynamic> messageJson;
|
||||||
|
if (message is String) {
|
||||||
|
messageJson = jsonDecode(message) as Map<String, dynamic>;
|
||||||
|
} else {
|
||||||
|
messageJson = message as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
final workerMethod = messageJson['method'] as String;
|
||||||
|
|
||||||
|
switch (workerMethod) {
|
||||||
|
case ElectrumWorkerMethods.connectionMethod:
|
||||||
|
await _handleConnect(
|
||||||
|
ElectrumWorkerConnectionRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumWorkerMethods.txHashMethod:
|
||||||
|
await _handleGetTxExpanded(
|
||||||
|
ElectrumWorkerTxExpandedRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.headersSubscribeMethod:
|
||||||
|
await _handleHeadersSubscribe(
|
||||||
|
ElectrumWorkerHeadersSubscribeRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.scripthashesSubscribeMethod:
|
||||||
|
await _handleScriphashesSubscribe(
|
||||||
|
ElectrumWorkerScripthashesSubscribeRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.getBalanceMethod:
|
||||||
|
await _handleGetBalance(
|
||||||
|
ElectrumWorkerGetBalanceRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.getHistoryMethod:
|
||||||
|
await _handleGetHistory(
|
||||||
|
ElectrumWorkerGetHistoryRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.listunspentMethod:
|
||||||
|
await _handleListUnspent(
|
||||||
|
ElectrumWorkerListUnspentRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.broadcastMethod:
|
||||||
|
await _handleBroadcast(
|
||||||
|
ElectrumWorkerBroadcastRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.tweaksSubscribeMethod:
|
||||||
|
await _handleScanSilentPayments(
|
||||||
|
ElectrumWorkerTweaksSubscribeRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.estimateFeeMethod:
|
||||||
|
await _handleGetFeeRates(
|
||||||
|
ElectrumWorkerGetFeesRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.versionMethod:
|
||||||
|
await _handleGetVersion(
|
||||||
|
ElectrumWorkerGetVersionRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
print(s);
|
||||||
|
_sendError(ElectrumWorkerErrorResponse(error: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleConnect(ElectrumWorkerConnectionRequest request) async {
|
||||||
|
_network = request.network;
|
||||||
|
|
||||||
|
_electrumClient = await ElectrumApiProvider.connect(
|
||||||
|
ElectrumTCPService.connect(
|
||||||
|
request.uri,
|
||||||
|
onConnectionStatusChange: (status) {
|
||||||
|
_sendResponse(ElectrumWorkerConnectionResponse(status: status, id: request.id));
|
||||||
|
},
|
||||||
|
defaultRequestTimeOut: const Duration(seconds: 5),
|
||||||
|
connectionTimeOut: const Duration(seconds: 5),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleHeadersSubscribe(ElectrumWorkerHeadersSubscribeRequest request) async {
|
||||||
|
final listener = _electrumClient!.subscribe(ElectrumHeaderSubscribe());
|
||||||
|
if (listener == null) {
|
||||||
|
_sendError(ElectrumWorkerHeadersSubscribeError(error: 'Failed to subscribe'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener((event) {
|
||||||
|
_sendResponse(
|
||||||
|
ElectrumWorkerHeadersSubscribeResponse(result: event, id: request.id),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleScriphashesSubscribe(
|
||||||
|
ElectrumWorkerScripthashesSubscribeRequest request,
|
||||||
|
) async {
|
||||||
|
await Future.wait(request.scripthashByAddress.entries.map((entry) async {
|
||||||
|
final address = entry.key;
|
||||||
|
final scripthash = entry.value;
|
||||||
|
|
||||||
|
final listener = await _electrumClient!.subscribe(
|
||||||
|
ElectrumScriptHashSubscribe(scriptHash: scripthash),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (listener == null) {
|
||||||
|
_sendError(ElectrumWorkerScripthashesSubscribeError(error: 'Failed to subscribe'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://electrumx.readthedocs.io/en/latest/protocol-basics.html#status
|
||||||
|
// The status of the script hash is the hash of the tx history, or null if the string is empty because there are no transactions
|
||||||
|
listener((status) async {
|
||||||
|
print("status: $status");
|
||||||
|
|
||||||
|
_sendResponse(ElectrumWorkerScripthashesSubscribeResponse(
|
||||||
|
result: {address: status},
|
||||||
|
id: request.id,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleGetHistory(ElectrumWorkerGetHistoryRequest result) async {
|
||||||
|
final Map<String, AddressHistoriesResponse> histories = {};
|
||||||
|
final addresses = result.addresses;
|
||||||
|
|
||||||
|
await Future.wait(addresses.map((addressRecord) async {
|
||||||
|
final history = await _electrumClient!.request(ElectrumScriptHashGetHistory(
|
||||||
|
scriptHash: addressRecord.scriptHash,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (history.isNotEmpty) {
|
||||||
|
addressRecord.setAsUsed();
|
||||||
|
addressRecord.txCount = history.length;
|
||||||
|
|
||||||
|
await Future.wait(history.map((transaction) async {
|
||||||
|
final txid = transaction['tx_hash'] as String;
|
||||||
|
final height = transaction['height'] as int;
|
||||||
|
late ElectrumTransactionInfo tx;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Exception thrown on null
|
||||||
|
tx = result.storedTxs.firstWhere((tx) => tx.id == txid);
|
||||||
|
|
||||||
|
if (height > 0) {
|
||||||
|
tx.height = height;
|
||||||
|
|
||||||
|
// the tx's block itself is the first confirmation so add 1
|
||||||
|
tx.confirmations = result.chainTip - height + 1;
|
||||||
|
tx.isPending = tx.confirmations == 0;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
tx = ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
|
await _getTransactionExpanded(
|
||||||
|
hash: txid,
|
||||||
|
currentChainTip: result.chainTip,
|
||||||
|
mempoolAPIEnabled: result.mempoolAPIEnabled,
|
||||||
|
),
|
||||||
|
result.walletType,
|
||||||
|
result.network,
|
||||||
|
addresses: result.addresses.map((addr) => addr.address).toSet(),
|
||||||
|
height: height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final addressHistories = histories[addressRecord.address];
|
||||||
|
if (addressHistories != null) {
|
||||||
|
addressHistories.txs.add(tx);
|
||||||
|
} else {
|
||||||
|
histories[addressRecord.address] = AddressHistoriesResponse(
|
||||||
|
addressRecord: addressRecord,
|
||||||
|
txs: [tx],
|
||||||
|
walletType: result.walletType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Future.value(null);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return histories;
|
||||||
|
}));
|
||||||
|
|
||||||
|
_sendResponse(ElectrumWorkerGetHistoryResponse(
|
||||||
|
result: histories.values.toList(),
|
||||||
|
id: result.id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future<void> _handleListUnspents(ElectrumWorkerGetBalanceRequest request) async {
|
||||||
|
// final balanceFutures = <Future<Map<String, dynamic>>>[];
|
||||||
|
|
||||||
|
// for (final scripthash in request.scripthashes) {
|
||||||
|
// final balanceFuture = _electrumClient!.request(
|
||||||
|
// ElectrumGetScriptHashBalance(scriptHash: scripthash),
|
||||||
|
// );
|
||||||
|
// balanceFutures.add(balanceFuture);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var totalConfirmed = 0;
|
||||||
|
// var totalUnconfirmed = 0;
|
||||||
|
|
||||||
|
// final balances = await Future.wait(balanceFutures);
|
||||||
|
|
||||||
|
// for (final balance in balances) {
|
||||||
|
// final confirmed = balance['confirmed'] as int? ?? 0;
|
||||||
|
// final unconfirmed = balance['unconfirmed'] as int? ?? 0;
|
||||||
|
// totalConfirmed += confirmed;
|
||||||
|
// totalUnconfirmed += unconfirmed;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _sendResponse(ElectrumWorkerGetBalanceResponse(
|
||||||
|
// result: ElectrumBalance(
|
||||||
|
// confirmed: totalConfirmed,
|
||||||
|
// unconfirmed: totalUnconfirmed,
|
||||||
|
// frozen: 0,
|
||||||
|
// ),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<void> _handleGetBalance(ElectrumWorkerGetBalanceRequest request) async {
|
||||||
|
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
||||||
|
|
||||||
|
for (final scripthash in request.scripthashes) {
|
||||||
|
final balanceFuture = _electrumClient!.request(
|
||||||
|
ElectrumGetScriptHashBalance(scriptHash: scripthash),
|
||||||
|
);
|
||||||
|
balanceFutures.add(balanceFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalConfirmed = 0;
|
||||||
|
var totalUnconfirmed = 0;
|
||||||
|
|
||||||
|
final balances = await Future.wait(balanceFutures);
|
||||||
|
|
||||||
|
for (final balance in balances) {
|
||||||
|
final confirmed = balance['confirmed'] as int? ?? 0;
|
||||||
|
final unconfirmed = balance['unconfirmed'] as int? ?? 0;
|
||||||
|
totalConfirmed += confirmed;
|
||||||
|
totalUnconfirmed += unconfirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendResponse(
|
||||||
|
ElectrumWorkerGetBalanceResponse(
|
||||||
|
result: ElectrumBalance(
|
||||||
|
confirmed: totalConfirmed,
|
||||||
|
unconfirmed: totalUnconfirmed,
|
||||||
|
frozen: 0,
|
||||||
|
),
|
||||||
|
id: request.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleListUnspent(ElectrumWorkerListUnspentRequest request) async {
|
||||||
|
final unspents = <String, List<ElectrumUtxo>>{};
|
||||||
|
|
||||||
|
await Future.wait(request.scripthashes.map((scriptHash) async {
|
||||||
|
final scriptHashUnspents = await _electrumClient!.request(
|
||||||
|
ElectrumScriptHashListUnspent(scriptHash: scriptHash),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (scriptHashUnspents.isNotEmpty) {
|
||||||
|
unspents[scriptHash] = scriptHashUnspents;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
_sendResponse(ElectrumWorkerListUnspentResponse(utxos: unspents, id: request.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleBroadcast(ElectrumWorkerBroadcastRequest request) async {
|
||||||
|
final txHash = await _electrumClient!.request(
|
||||||
|
ElectrumBroadCastTransaction(transactionRaw: request.transactionRaw),
|
||||||
|
);
|
||||||
|
|
||||||
|
_sendResponse(ElectrumWorkerBroadcastResponse(txHash: txHash, id: request.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleGetTxExpanded(ElectrumWorkerTxExpandedRequest request) async {
|
||||||
|
final tx = await _getTransactionExpanded(
|
||||||
|
hash: request.txHash,
|
||||||
|
currentChainTip: request.currentChainTip,
|
||||||
|
mempoolAPIEnabled: false,
|
||||||
|
getConfirmations: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
_sendResponse(ElectrumWorkerTxExpandedResponse(expandedTx: tx, id: request.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ElectrumTransactionBundle> _getTransactionExpanded({
|
||||||
|
required String hash,
|
||||||
|
required int currentChainTip,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
|
bool getConfirmations = true,
|
||||||
|
}) async {
|
||||||
|
int? time;
|
||||||
|
int? height;
|
||||||
|
int? confirmations;
|
||||||
|
|
||||||
|
final transactionHex = await _electrumClient!.request(
|
||||||
|
ElectrumGetTransactionHex(transactionHash: hash),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getConfirmations) {
|
||||||
|
if (mempoolAPIEnabled) {
|
||||||
|
try {
|
||||||
|
final txVerbose = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
"http://mempool.cakewallet.com:8999/api/v1/tx/$hash/status",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (txVerbose.statusCode == 200 &&
|
||||||
|
txVerbose.body.isNotEmpty &&
|
||||||
|
jsonDecode(txVerbose.body) != null) {
|
||||||
|
height = jsonDecode(txVerbose.body)['block_height'] as int;
|
||||||
|
|
||||||
|
final blockHash = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (blockHash.statusCode == 200 &&
|
||||||
|
blockHash.body.isNotEmpty &&
|
||||||
|
jsonDecode(blockHash.body) != null) {
|
||||||
|
final blockResponse = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (blockResponse.statusCode == 200 &&
|
||||||
|
blockResponse.body.isNotEmpty &&
|
||||||
|
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
||||||
|
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height != null) {
|
||||||
|
if (time == null && height > 0) {
|
||||||
|
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
final tip = currentChainTip;
|
||||||
|
if (tip > 0 && height > 0) {
|
||||||
|
// Add one because the block itself is the first confirmation
|
||||||
|
confirmations = tip - height + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final original = BtcTransaction.fromRaw(transactionHex);
|
||||||
|
final ins = <BtcTransaction>[];
|
||||||
|
|
||||||
|
for (final vin in original.inputs) {
|
||||||
|
final inputTransactionHex = await _electrumClient!.request(
|
||||||
|
ElectrumGetTransactionHex(transactionHash: vin.txId),
|
||||||
|
);
|
||||||
|
|
||||||
|
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ElectrumTransactionBundle(
|
||||||
|
original,
|
||||||
|
ins: ins,
|
||||||
|
time: time,
|
||||||
|
confirmations: confirmations ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleGetFeeRates(ElectrumWorkerGetFeesRequest request) async {
|
||||||
|
if (request.mempoolAPIEnabled) {
|
||||||
|
try {
|
||||||
|
final recommendedFees = await ApiProvider.fromMempool(
|
||||||
|
_network!,
|
||||||
|
baseUrl: "http://mempool.cakewallet.com:8999/api",
|
||||||
|
).getRecommendedFeeRate();
|
||||||
|
|
||||||
|
final unimportantFee = recommendedFees.economyFee!.satoshis;
|
||||||
|
final normalFee = recommendedFees.low.satoshis;
|
||||||
|
int elevatedFee = recommendedFees.medium.satoshis;
|
||||||
|
int priorityFee = recommendedFees.high.satoshis;
|
||||||
|
|
||||||
|
// Bitcoin only: adjust fee rates to avoid equal fee values
|
||||||
|
// elevated fee should be higher than normal fee
|
||||||
|
if (normalFee == elevatedFee) {
|
||||||
|
elevatedFee++;
|
||||||
|
}
|
||||||
|
// priority fee should be higher than elevated fee
|
||||||
|
while (priorityFee <= elevatedFee) {
|
||||||
|
priorityFee++;
|
||||||
|
}
|
||||||
|
// this guarantees that, even if all fees are low and equal,
|
||||||
|
// higher priority fee txs can be consumed when chain fees start surging
|
||||||
|
|
||||||
|
_sendResponse(
|
||||||
|
ElectrumWorkerGetFeesResponse(
|
||||||
|
result: BitcoinTransactionPriorities(
|
||||||
|
unimportant: unimportantFee,
|
||||||
|
normal: normalFee,
|
||||||
|
elevated: elevatedFee,
|
||||||
|
priority: priorityFee,
|
||||||
|
custom: unimportantFee,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
_sendError(ElectrumWorkerGetFeesError(error: e.toString()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_sendResponse(
|
||||||
|
ElectrumWorkerGetFeesResponse(
|
||||||
|
result: ElectrumTransactionPriorities.fromList(
|
||||||
|
await _electrumClient!.getFeeRates(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleScanSilentPayments(ElectrumWorkerTweaksSubscribeRequest request) async {
|
||||||
|
final scanData = request.scanData;
|
||||||
|
int syncHeight = scanData.height;
|
||||||
|
int initialSyncHeight = syncHeight;
|
||||||
|
|
||||||
|
int getCountPerRequest(int syncHeight) {
|
||||||
|
if (scanData.isSingleScan) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final amountLeft = scanData.chainTip - syncHeight + 1;
|
||||||
|
return amountLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
final receiver = Receiver(
|
||||||
|
scanData.silentAddress.b_scan.toHex(),
|
||||||
|
scanData.silentAddress.B_spend.toHex(),
|
||||||
|
scanData.network == BitcoinNetwork.testnet,
|
||||||
|
scanData.labelIndexes,
|
||||||
|
scanData.labelIndexes.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initial status UI update, send how many blocks in total to scan
|
||||||
|
final initialCount = getCountPerRequest(syncHeight);
|
||||||
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
|
result: TweaksSyncResponse(
|
||||||
|
height: syncHeight,
|
||||||
|
syncStatus: StartingScanSyncStatus(syncHeight),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
final listener = await _electrumClient!.subscribe(
|
||||||
|
ElectrumTweaksSubscribe(height: syncHeight, count: initialCount),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> listenFn(ElectrumTweaksSubscribeResponse response) async {
|
||||||
|
// success or error msg
|
||||||
|
final noData = response.message != null;
|
||||||
|
|
||||||
|
if (noData) {
|
||||||
|
// re-subscribe to continue receiving messages, starting from the next unscanned height
|
||||||
|
final nextHeight = syncHeight + 1;
|
||||||
|
final nextCount = getCountPerRequest(nextHeight);
|
||||||
|
|
||||||
|
if (nextCount > 0) {
|
||||||
|
final nextListener = await _electrumClient!.subscribe(
|
||||||
|
ElectrumTweaksSubscribe(height: syncHeight, count: initialCount),
|
||||||
|
);
|
||||||
|
nextListener?.call(listenFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuous status UI update, send how many blocks left to scan
|
||||||
|
final syncingStatus = scanData.isSingleScan
|
||||||
|
? SyncingSyncStatus(1, 0)
|
||||||
|
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
|
||||||
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
|
result: TweaksSyncResponse(height: syncHeight, syncStatus: syncingStatus),
|
||||||
|
));
|
||||||
|
|
||||||
|
final tweakHeight = response.block;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final blockTweaks = response.blockTweaks;
|
||||||
|
|
||||||
|
for (final txid in blockTweaks.keys) {
|
||||||
|
final tweakData = blockTweaks[txid];
|
||||||
|
final outputPubkeys = tweakData!.outputPubkeys;
|
||||||
|
final tweak = tweakData.tweak;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// scanOutputs called from rust here
|
||||||
|
final addToWallet = scanOutputs(outputPubkeys.keys.toList(), tweak, receiver);
|
||||||
|
|
||||||
|
if (addToWallet.isEmpty) {
|
||||||
|
// no results tx, continue to next tx
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s)
|
||||||
|
final txInfo = ElectrumTransactionInfo(
|
||||||
|
WalletType.bitcoin,
|
||||||
|
id: txid,
|
||||||
|
height: tweakHeight,
|
||||||
|
amount: 0,
|
||||||
|
fee: 0,
|
||||||
|
direction: TransactionDirection.incoming,
|
||||||
|
isPending: false,
|
||||||
|
isReplaced: false,
|
||||||
|
date: scanData.network == BitcoinNetwork.mainnet
|
||||||
|
? getDateByBitcoinHeight(tweakHeight)
|
||||||
|
: DateTime.now(),
|
||||||
|
confirmations: scanData.chainTip - tweakHeight + 1,
|
||||||
|
unspents: [],
|
||||||
|
isReceivedSilentPayment: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
addToWallet.forEach((label, value) {
|
||||||
|
(value as Map<String, dynamic>).forEach((output, tweak) {
|
||||||
|
final t_k = tweak.toString();
|
||||||
|
|
||||||
|
final receivingOutputAddress = ECPublic.fromHex(output)
|
||||||
|
.toTaprootAddress(tweak: false)
|
||||||
|
.toAddress(scanData.network);
|
||||||
|
|
||||||
|
final matchingOutput = outputPubkeys[output]!;
|
||||||
|
final amount = matchingOutput.amount;
|
||||||
|
final pos = matchingOutput.vout;
|
||||||
|
|
||||||
|
final receivedAddressRecord = BitcoinReceivedSPAddressRecord(
|
||||||
|
receivingOutputAddress,
|
||||||
|
labelIndex: 1, // TODO: get actual index/label
|
||||||
|
isUsed: true,
|
||||||
|
spendKey: scanData.silentAddress.b_spend.tweakAdd(
|
||||||
|
BigintUtils.fromBytes(BytesUtils.fromHexString(t_k)),
|
||||||
|
),
|
||||||
|
txCount: 1,
|
||||||
|
balance: amount,
|
||||||
|
);
|
||||||
|
|
||||||
|
final unspent = BitcoinUnspent(receivedAddressRecord, txid, amount, pos);
|
||||||
|
|
||||||
|
txInfo.unspents!.add(unspent);
|
||||||
|
txInfo.amount += unspent.value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
|
result: TweaksSyncResponse(transactions: {txInfo.id: txInfo}),
|
||||||
|
));
|
||||||
|
} catch (e, stacktrace) {
|
||||||
|
print(stacktrace);
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, stacktrace) {
|
||||||
|
print(stacktrace);
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
syncHeight = tweakHeight;
|
||||||
|
|
||||||
|
if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) {
|
||||||
|
if (tweakHeight >= scanData.chainTip)
|
||||||
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
|
result: TweaksSyncResponse(
|
||||||
|
height: syncHeight,
|
||||||
|
syncStatus: SyncedTipSyncStatus(scanData.chainTip),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (scanData.isSingleScan) {
|
||||||
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
|
result: TweaksSyncResponse(height: syncHeight, syncStatus: SyncedSyncStatus()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listener?.call(listenFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async {
|
||||||
|
_sendResponse(ElectrumWorkerGetVersionResponse(
|
||||||
|
result: (await _electrumClient!.request(
|
||||||
|
ElectrumVersion(
|
||||||
|
clientName: "",
|
||||||
|
protocolVersion: ["1.4"],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
id: request.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> delegatedScan(ScanData scanData) async {
|
||||||
|
// int syncHeight = scanData.height;
|
||||||
|
// int initialSyncHeight = syncHeight;
|
||||||
|
|
||||||
|
// BehaviorSubject<Object>? tweaksSubscription = null;
|
||||||
|
|
||||||
|
// final electrumClient = scanData.electrumClient;
|
||||||
|
// await electrumClient.connectToUri(
|
||||||
|
// scanData.node?.uri ?? Uri.parse("tcp://electrs.cakewallet.com:50001"),
|
||||||
|
// useSSL: scanData.node?.useSSL ?? false,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (tweaksSubscription == null) {
|
||||||
|
// scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight)));
|
||||||
|
|
||||||
|
// tweaksSubscription = await electrumClient.tweaksScan(
|
||||||
|
// pubSpendKey: scanData.silentAddress.B_spend.toHex(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Future<void> listenFn(t) async {
|
||||||
|
// final tweaks = t as Map<String, dynamic>;
|
||||||
|
// final msg = tweaks["message"];
|
||||||
|
|
||||||
|
// // success or error msg
|
||||||
|
// final noData = msg != null;
|
||||||
|
// if (noData) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Continuous status UI update, send how many blocks left to scan
|
||||||
|
// final syncingStatus = scanData.isSingleScan
|
||||||
|
// ? SyncingSyncStatus(1, 0)
|
||||||
|
// : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
|
||||||
|
// scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
|
||||||
|
|
||||||
|
// final blockHeight = tweaks.keys.first;
|
||||||
|
// final tweakHeight = int.parse(blockHeight);
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// final blockTweaks = tweaks[blockHeight] as Map<String, dynamic>;
|
||||||
|
|
||||||
|
// for (var j = 0; j < blockTweaks.keys.length; j++) {
|
||||||
|
// final txid = blockTweaks.keys.elementAt(j);
|
||||||
|
// final details = blockTweaks[txid] as Map<String, dynamic>;
|
||||||
|
// final outputPubkeys = (details["output_pubkeys"] as Map<dynamic, dynamic>);
|
||||||
|
// final spendingKey = details["spending_key"].toString();
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// // placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s)
|
||||||
|
// final txInfo = ElectrumTransactionInfo(
|
||||||
|
// WalletType.bitcoin,
|
||||||
|
// id: txid,
|
||||||
|
// height: tweakHeight,
|
||||||
|
// amount: 0,
|
||||||
|
// fee: 0,
|
||||||
|
// direction: TransactionDirection.incoming,
|
||||||
|
// isPending: false,
|
||||||
|
// isReplaced: false,
|
||||||
|
// date: scanData.network == BitcoinNetwork.mainnet
|
||||||
|
// ? getDateByBitcoinHeight(tweakHeight)
|
||||||
|
// : DateTime.now(),
|
||||||
|
// confirmations: scanData.chainTip - tweakHeight + 1,
|
||||||
|
// unspents: [],
|
||||||
|
// isReceivedSilentPayment: true,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// outputPubkeys.forEach((pos, value) {
|
||||||
|
// final secKey = ECPrivate.fromHex(spendingKey);
|
||||||
|
// final receivingOutputAddress =
|
||||||
|
// secKey.getPublic().toTaprootAddress(tweak: false).toAddress(scanData.network);
|
||||||
|
|
||||||
|
// late int amount;
|
||||||
|
// try {
|
||||||
|
// amount = int.parse(value[1].toString());
|
||||||
|
// } catch (_) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// final receivedAddressRecord = BitcoinReceivedSPAddressRecord(
|
||||||
|
// receivingOutputAddress,
|
||||||
|
// labelIndex: 0,
|
||||||
|
// isUsed: true,
|
||||||
|
// spendKey: secKey,
|
||||||
|
// txCount: 1,
|
||||||
|
// balance: amount,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// final unspent = BitcoinUnspent(
|
||||||
|
// receivedAddressRecord,
|
||||||
|
// txid,
|
||||||
|
// amount,
|
||||||
|
// int.parse(pos.toString()),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// txInfo.unspents!.add(unspent);
|
||||||
|
// txInfo.amount += unspent.value;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// scanData.sendPort.send({txInfo.id: txInfo});
|
||||||
|
// } catch (_) {}
|
||||||
|
// }
|
||||||
|
// } catch (_) {}
|
||||||
|
|
||||||
|
// syncHeight = tweakHeight;
|
||||||
|
|
||||||
|
// if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) {
|
||||||
|
// if (tweakHeight >= scanData.chainTip)
|
||||||
|
// scanData.sendPort.send(SyncResponse(
|
||||||
|
// syncHeight,
|
||||||
|
// SyncedTipSyncStatus(scanData.chainTip),
|
||||||
|
// ));
|
||||||
|
|
||||||
|
// if (scanData.isSingleScan) {
|
||||||
|
// scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus()));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await tweaksSubscription!.close();
|
||||||
|
// await electrumClient.close();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// tweaksSubscription?.listen(listenFn);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (tweaksSubscription == null) {
|
||||||
|
// return scanData.sendPort.send(
|
||||||
|
// SyncResponse(syncHeight, UnsupportedSyncStatus()),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanNode {
|
||||||
|
final Uri uri;
|
||||||
|
final bool? useSSL;
|
||||||
|
|
||||||
|
ScanNode(this.uri, this.useSSL);
|
||||||
|
}
|
17
cw_bitcoin/lib/electrum_worker/electrum_worker_methods.dart
Normal file
17
cw_bitcoin/lib/electrum_worker/electrum_worker_methods.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
class ElectrumWorkerMethods {
|
||||||
|
const ElectrumWorkerMethods._(this.method);
|
||||||
|
final String method;
|
||||||
|
|
||||||
|
static const String connectionMethod = "connection";
|
||||||
|
static const String unknownMethod = "unknown";
|
||||||
|
static const String txHashMethod = "txHash";
|
||||||
|
|
||||||
|
static const ElectrumWorkerMethods connect = ElectrumWorkerMethods._(connectionMethod);
|
||||||
|
static const ElectrumWorkerMethods unknown = ElectrumWorkerMethods._(unknownMethod);
|
||||||
|
static const ElectrumWorkerMethods txHash = ElectrumWorkerMethods._(txHashMethod);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
53
cw_bitcoin/lib/electrum_worker/electrum_worker_params.dart
Normal file
53
cw_bitcoin/lib/electrum_worker/electrum_worker_params.dart
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
|
||||||
|
|
||||||
|
abstract class ElectrumWorkerRequest {
|
||||||
|
abstract final String method;
|
||||||
|
abstract final int? id;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
ElectrumWorkerRequest.fromJson(Map<String, dynamic> json);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerResponse<RESULT, RESPONSE> {
|
||||||
|
ElectrumWorkerResponse({
|
||||||
|
required this.method,
|
||||||
|
required this.result,
|
||||||
|
this.error,
|
||||||
|
this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String method;
|
||||||
|
final RESULT result;
|
||||||
|
final String? error;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
RESPONSE resultJson(RESULT result) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
factory ElectrumWorkerResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'result': resultJson(result), 'error': error, 'id': id};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerErrorResponse({required this.error, this.id});
|
||||||
|
|
||||||
|
String get method => ElectrumWorkerMethods.unknown.method;
|
||||||
|
final int? id;
|
||||||
|
final String error;
|
||||||
|
|
||||||
|
factory ElectrumWorkerErrorResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerErrorResponse(error: json['error'] as String, id: json['id'] as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'error': error, 'id': id};
|
||||||
|
}
|
||||||
|
}
|
56
cw_bitcoin/lib/electrum_worker/methods/broadcast.dart
Normal file
56
cw_bitcoin/lib/electrum_worker/methods/broadcast.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerBroadcastRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerBroadcastRequest({required this.transactionRaw, this.id});
|
||||||
|
|
||||||
|
final String transactionRaw;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.broadcast.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerBroadcastRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerBroadcastRequest(
|
||||||
|
transactionRaw: json['transactionRaw'] as String,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'transactionRaw': transactionRaw};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerBroadcastError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerBroadcastError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get method => ElectrumRequestMethods.broadcast.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerBroadcastResponse extends ElectrumWorkerResponse<String, String> {
|
||||||
|
ElectrumWorkerBroadcastResponse({
|
||||||
|
required String txHash,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(result: txHash, method: ElectrumRequestMethods.broadcast.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String resultJson(result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerBroadcastResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerBroadcastResponse(
|
||||||
|
txHash: json['result'] as String,
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
73
cw_bitcoin/lib/electrum_worker/methods/connection.dart
Normal file
73
cw_bitcoin/lib/electrum_worker/methods/connection.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerConnectionRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerConnectionRequest({
|
||||||
|
required this.uri,
|
||||||
|
required this.network,
|
||||||
|
this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Uri uri;
|
||||||
|
final BasedUtxoNetwork network;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumWorkerMethods.connect.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerConnectionRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerConnectionRequest(
|
||||||
|
uri: Uri.parse(json['uri'] as String),
|
||||||
|
network: BasedUtxoNetwork.values.firstWhere(
|
||||||
|
(e) => e.toString() == json['network'] as String,
|
||||||
|
),
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'method': method,
|
||||||
|
'uri': uri.toString(),
|
||||||
|
'network': network.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerConnectionError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerConnectionError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get method => ElectrumWorkerMethods.connect.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerConnectionResponse extends ElectrumWorkerResponse<ConnectionStatus, String> {
|
||||||
|
ElectrumWorkerConnectionResponse({
|
||||||
|
required ConnectionStatus status,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(
|
||||||
|
result: status,
|
||||||
|
method: ElectrumWorkerMethods.connect.method,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String resultJson(result) {
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerConnectionResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerConnectionResponse(
|
||||||
|
status: ConnectionStatus.values.firstWhere(
|
||||||
|
(e) => e.toString() == json['result'] as String,
|
||||||
|
),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
61
cw_bitcoin/lib/electrum_worker/methods/get_balance.dart
Normal file
61
cw_bitcoin/lib/electrum_worker/methods/get_balance.dart
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerGetBalanceRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerGetBalanceRequest({required this.scripthashes, this.id});
|
||||||
|
|
||||||
|
final Set<String> scripthashes;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.getBalance.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetBalanceRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetBalanceRequest(
|
||||||
|
scripthashes: (json['scripthashes'] as List<String>).toSet(),
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'scripthashes': scripthashes.toList()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetBalanceError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerGetBalanceError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.getBalance.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetBalanceResponse
|
||||||
|
extends ElectrumWorkerResponse<ElectrumBalance, Map<String, int>?> {
|
||||||
|
ElectrumWorkerGetBalanceResponse({
|
||||||
|
required super.result,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(method: ElectrumRequestMethods.getBalance.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, int>? resultJson(result) {
|
||||||
|
return {"confirmed": result.confirmed, "unconfirmed": result.unconfirmed};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetBalanceResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetBalanceResponse(
|
||||||
|
result: ElectrumBalance(
|
||||||
|
confirmed: json['result']['confirmed'] as int,
|
||||||
|
unconfirmed: json['result']['unconfirmed'] as int,
|
||||||
|
frozen: 0,
|
||||||
|
),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
60
cw_bitcoin/lib/electrum_worker/methods/get_fees.dart
Normal file
60
cw_bitcoin/lib/electrum_worker/methods/get_fees.dart
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerGetFeesRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerGetFeesRequest({
|
||||||
|
required this.mempoolAPIEnabled,
|
||||||
|
this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool mempoolAPIEnabled;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.estimateFee.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetFeesRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetFeesRequest(
|
||||||
|
mempoolAPIEnabled: json['mempoolAPIEnabled'] as bool,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'mempoolAPIEnabled': mempoolAPIEnabled};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetFeesError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerGetFeesError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get method => ElectrumRequestMethods.estimateFee.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetFeesResponse
|
||||||
|
extends ElectrumWorkerResponse<TransactionPriorities, Map<String, int>> {
|
||||||
|
ElectrumWorkerGetFeesResponse({
|
||||||
|
required super.result,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(method: ElectrumRequestMethods.estimateFee.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, int> resultJson(result) {
|
||||||
|
return result.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetFeesResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetFeesResponse(
|
||||||
|
result: deserializeTransactionPriorities(json['result'] as Map<String, dynamic>),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
120
cw_bitcoin/lib/electrum_worker/methods/get_history.dart
Normal file
120
cw_bitcoin/lib/electrum_worker/methods/get_history.dart
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerGetHistoryRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerGetHistoryRequest({
|
||||||
|
required this.addresses,
|
||||||
|
required this.storedTxs,
|
||||||
|
required this.walletType,
|
||||||
|
required this.chainTip,
|
||||||
|
required this.network,
|
||||||
|
required this.mempoolAPIEnabled,
|
||||||
|
this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<BitcoinAddressRecord> addresses;
|
||||||
|
final List<ElectrumTransactionInfo> storedTxs;
|
||||||
|
final WalletType walletType;
|
||||||
|
final int chainTip;
|
||||||
|
final BasedUtxoNetwork network;
|
||||||
|
final bool mempoolAPIEnabled;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.getHistory.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetHistoryRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
final walletType = WalletType.values[json['walletType'] as int];
|
||||||
|
|
||||||
|
return ElectrumWorkerGetHistoryRequest(
|
||||||
|
addresses: (json['addresses'] as List)
|
||||||
|
.map((e) => BitcoinAddressRecord.fromJSON(e as String))
|
||||||
|
.toList(),
|
||||||
|
storedTxs: (json['storedTxIds'] as List)
|
||||||
|
.map((e) => ElectrumTransactionInfo.fromJson(e as Map<String, dynamic>, walletType))
|
||||||
|
.toList(),
|
||||||
|
walletType: walletType,
|
||||||
|
chainTip: json['chainTip'] as int,
|
||||||
|
network: BasedUtxoNetwork.fromName(json['network'] as String),
|
||||||
|
mempoolAPIEnabled: json['mempoolAPIEnabled'] as bool,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'method': method,
|
||||||
|
'addresses': addresses.map((e) => e.toJSON()).toList(),
|
||||||
|
'storedTxIds': storedTxs.map((e) => e.toJson()).toList(),
|
||||||
|
'walletType': walletType.index,
|
||||||
|
'chainTip': chainTip,
|
||||||
|
'network': network.value,
|
||||||
|
'mempoolAPIEnabled': mempoolAPIEnabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetHistoryError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerGetHistoryError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.getHistory.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddressHistoriesResponse {
|
||||||
|
final BitcoinAddressRecord addressRecord;
|
||||||
|
final List<ElectrumTransactionInfo> txs;
|
||||||
|
final WalletType walletType;
|
||||||
|
|
||||||
|
AddressHistoriesResponse(
|
||||||
|
{required this.addressRecord, required this.txs, required this.walletType});
|
||||||
|
|
||||||
|
factory AddressHistoriesResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
final walletType = WalletType.values[json['walletType'] as int];
|
||||||
|
|
||||||
|
return AddressHistoriesResponse(
|
||||||
|
addressRecord: BitcoinAddressRecord.fromJSON(json['address'] as String),
|
||||||
|
txs: (json['txs'] as List)
|
||||||
|
.map((e) => ElectrumTransactionInfo.fromJson(e as Map<String, dynamic>, walletType))
|
||||||
|
.toList(),
|
||||||
|
walletType: walletType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'address': addressRecord.toJSON(),
|
||||||
|
'txs': txs.map((e) => e.toJson()).toList(),
|
||||||
|
'walletType': walletType.index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetHistoryResponse
|
||||||
|
extends ElectrumWorkerResponse<List<AddressHistoriesResponse>, List<Map<String, dynamic>>> {
|
||||||
|
ElectrumWorkerGetHistoryResponse({
|
||||||
|
required super.result,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(method: ElectrumRequestMethods.getHistory.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Map<String, dynamic>> resultJson(result) {
|
||||||
|
return result.map((e) => e.toJson()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetHistoryResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetHistoryResponse(
|
||||||
|
result: (json['result'] as List)
|
||||||
|
.map((e) => AddressHistoriesResponse.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
63
cw_bitcoin/lib/electrum_worker/methods/get_tx_expanded.dart
Normal file
63
cw_bitcoin/lib/electrum_worker/methods/get_tx_expanded.dart
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerTxExpandedRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerTxExpandedRequest({
|
||||||
|
required this.txHash,
|
||||||
|
required this.currentChainTip,
|
||||||
|
this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String txHash;
|
||||||
|
final int currentChainTip;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumWorkerMethods.txHash.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerTxExpandedRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerTxExpandedRequest(
|
||||||
|
txHash: json['txHash'] as String,
|
||||||
|
currentChainTip: json['currentChainTip'] as int,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'txHash': txHash, 'currentChainTip': currentChainTip};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerTxExpandedError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerTxExpandedError({
|
||||||
|
required String error,
|
||||||
|
super.id,
|
||||||
|
}) : super(error: error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get method => ElectrumWorkerMethods.txHash.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerTxExpandedResponse
|
||||||
|
extends ElectrumWorkerResponse<ElectrumTransactionBundle, Map<String, dynamic>> {
|
||||||
|
ElectrumWorkerTxExpandedResponse({
|
||||||
|
required ElectrumTransactionBundle expandedTx,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(result: expandedTx, method: ElectrumWorkerMethods.txHash.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> resultJson(result) {
|
||||||
|
return result.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerTxExpandedResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerTxExpandedResponse(
|
||||||
|
expandedTx: ElectrumTransactionBundle.fromJson(json['result'] as Map<String, dynamic>),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerHeadersSubscribeRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerHeadersSubscribeRequest({this.id});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.headersSubscribe.method;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerHeadersSubscribeRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerHeadersSubscribeRequest(
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerHeadersSubscribeError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerHeadersSubscribeError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.headersSubscribe.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerHeadersSubscribeResponse
|
||||||
|
extends ElectrumWorkerResponse<ElectrumHeaderResponse, Map<String, dynamic>> {
|
||||||
|
ElectrumWorkerHeadersSubscribeResponse({
|
||||||
|
required super.result,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(method: ElectrumRequestMethods.headersSubscribe.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> resultJson(result) {
|
||||||
|
return result.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerHeadersSubscribeResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerHeadersSubscribeResponse(
|
||||||
|
result: ElectrumHeaderResponse.fromJson(json['result'] as Map<String, dynamic>),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
60
cw_bitcoin/lib/electrum_worker/methods/list_unspent.dart
Normal file
60
cw_bitcoin/lib/electrum_worker/methods/list_unspent.dart
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerListUnspentRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerListUnspentRequest({required this.scripthashes, this.id});
|
||||||
|
|
||||||
|
final List<String> scripthashes;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.listunspent.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerListUnspentRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerListUnspentRequest(
|
||||||
|
scripthashes: json['scripthashes'] as List<String>,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'scripthashes': scripthashes};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerListUnspentError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerListUnspentError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get method => ElectrumRequestMethods.listunspent.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerListUnspentResponse
|
||||||
|
extends ElectrumWorkerResponse<Map<String, List<ElectrumUtxo>>, Map<String, dynamic>> {
|
||||||
|
ElectrumWorkerListUnspentResponse({
|
||||||
|
required Map<String, List<ElectrumUtxo>> utxos,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(result: utxos, method: ElectrumRequestMethods.listunspent.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> resultJson(result) {
|
||||||
|
return result.map((key, value) => MapEntry(key, value.map((e) => e.toJson()).toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerListUnspentResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerListUnspentResponse(
|
||||||
|
utxos: (json['result'] as Map<String, dynamic>).map(
|
||||||
|
(key, value) => MapEntry(key,
|
||||||
|
(value as List).map((e) => ElectrumUtxo.fromJson(e as Map<String, dynamic>)).toList()),
|
||||||
|
),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
cw_bitcoin/lib/electrum_worker/methods/methods.dart
Normal file
22
cw_bitcoin/lib/electrum_worker/methods/methods.dart
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:cw_core/sync_status.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
|
||||||
|
part 'connection.dart';
|
||||||
|
part 'headers_subscribe.dart';
|
||||||
|
part 'scripthashes_subscribe.dart';
|
||||||
|
part 'get_balance.dart';
|
||||||
|
part 'get_history.dart';
|
||||||
|
part 'get_tx_expanded.dart';
|
||||||
|
part 'broadcast.dart';
|
||||||
|
part 'list_unspent.dart';
|
||||||
|
part 'tweaks_subscribe.dart';
|
||||||
|
part 'get_fees.dart';
|
||||||
|
part 'version.dart';
|
|
@ -0,0 +1,60 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerScripthashesSubscribeRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerScripthashesSubscribeRequest({
|
||||||
|
required this.scripthashByAddress,
|
||||||
|
this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Map<String, String> scripthashByAddress;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.scriptHashSubscribe.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerScripthashesSubscribeRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerScripthashesSubscribeRequest(
|
||||||
|
scripthashByAddress: json['scripthashes'] as Map<String, String>,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'scripthashes': scripthashByAddress};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerScripthashesSubscribeError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerScripthashesSubscribeError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.scriptHashSubscribe.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerScripthashesSubscribeResponse
|
||||||
|
extends ElectrumWorkerResponse<Map<String, String>?, Map<String, String>?> {
|
||||||
|
ElectrumWorkerScripthashesSubscribeResponse({
|
||||||
|
required super.result,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(method: ElectrumRequestMethods.scriptHashSubscribe.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String>? resultJson(result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerScripthashesSubscribeResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerScripthashesSubscribeResponse(
|
||||||
|
result: json['result'] as Map<String, String>?,
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
157
cw_bitcoin/lib/electrum_worker/methods/tweaks_subscribe.dart
Normal file
157
cw_bitcoin/lib/electrum_worker/methods/tweaks_subscribe.dart
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ScanData {
|
||||||
|
final SilentPaymentOwner silentAddress;
|
||||||
|
final int height;
|
||||||
|
final BasedUtxoNetwork network;
|
||||||
|
final int chainTip;
|
||||||
|
final List<String> transactionHistoryIds;
|
||||||
|
final Map<String, String> labels;
|
||||||
|
final List<int> labelIndexes;
|
||||||
|
final bool isSingleScan;
|
||||||
|
|
||||||
|
ScanData({
|
||||||
|
required this.silentAddress,
|
||||||
|
required this.height,
|
||||||
|
required this.network,
|
||||||
|
required this.chainTip,
|
||||||
|
required this.transactionHistoryIds,
|
||||||
|
required this.labels,
|
||||||
|
required this.labelIndexes,
|
||||||
|
required this.isSingleScan,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ScanData.fromHeight(ScanData scanData, int newHeight) {
|
||||||
|
return ScanData(
|
||||||
|
silentAddress: scanData.silentAddress,
|
||||||
|
height: newHeight,
|
||||||
|
network: scanData.network,
|
||||||
|
chainTip: scanData.chainTip,
|
||||||
|
transactionHistoryIds: scanData.transactionHistoryIds,
|
||||||
|
labels: scanData.labels,
|
||||||
|
labelIndexes: scanData.labelIndexes,
|
||||||
|
isSingleScan: scanData.isSingleScan,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'silentAddress': silentAddress.toJson(),
|
||||||
|
'height': height,
|
||||||
|
'network': network.value,
|
||||||
|
'chainTip': chainTip,
|
||||||
|
'transactionHistoryIds': transactionHistoryIds,
|
||||||
|
'labels': labels,
|
||||||
|
'labelIndexes': labelIndexes,
|
||||||
|
'isSingleScan': isSingleScan,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static ScanData fromJson(Map<String, dynamic> json) {
|
||||||
|
return ScanData(
|
||||||
|
silentAddress: SilentPaymentOwner.fromJson(json['silentAddress'] as Map<String, dynamic>),
|
||||||
|
height: json['height'] as int,
|
||||||
|
network: BasedUtxoNetwork.fromName(json['network'] as String),
|
||||||
|
chainTip: json['chainTip'] as int,
|
||||||
|
transactionHistoryIds:
|
||||||
|
(json['transactionHistoryIds'] as List).map((e) => e as String).toList(),
|
||||||
|
labels: json['labels'] as Map<String, String>,
|
||||||
|
labelIndexes: (json['labelIndexes'] as List).map((e) => e as int).toList(),
|
||||||
|
isSingleScan: json['isSingleScan'] as bool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerTweaksSubscribeRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerTweaksSubscribeRequest({
|
||||||
|
required this.scanData,
|
||||||
|
this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ScanData scanData;
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.tweaksSubscribe.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerTweaksSubscribeRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerTweaksSubscribeRequest(
|
||||||
|
scanData: ScanData.fromJson(json['scanData'] as Map<String, dynamic>),
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'scanData': scanData.toJson()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerTweaksSubscribeError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerTweaksSubscribeError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.tweaksSubscribe.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TweaksSyncResponse {
|
||||||
|
int? height;
|
||||||
|
SyncStatus? syncStatus;
|
||||||
|
Map<String, ElectrumTransactionInfo>? transactions = {};
|
||||||
|
|
||||||
|
TweaksSyncResponse({this.height, this.syncStatus, this.transactions});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'height': height,
|
||||||
|
'syncStatus': syncStatus == null ? null : syncStatusToJson(syncStatus!),
|
||||||
|
'transactions': transactions?.map((key, value) => MapEntry(key, value.toJson())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static TweaksSyncResponse fromJson(Map<String, dynamic> json) {
|
||||||
|
return TweaksSyncResponse(
|
||||||
|
height: json['height'] as int?,
|
||||||
|
syncStatus: json['syncStatus'] == null
|
||||||
|
? null
|
||||||
|
: syncStatusFromJson(json['syncStatus'] as Map<String, dynamic>),
|
||||||
|
transactions: json['transactions'] == null
|
||||||
|
? null
|
||||||
|
: (json['transactions'] as Map<String, dynamic>).map(
|
||||||
|
(key, value) => MapEntry(
|
||||||
|
key,
|
||||||
|
ElectrumTransactionInfo.fromJson(
|
||||||
|
value as Map<String, dynamic>,
|
||||||
|
WalletType.bitcoin,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerTweaksSubscribeResponse
|
||||||
|
extends ElectrumWorkerResponse<TweaksSyncResponse, Map<String, dynamic>> {
|
||||||
|
ElectrumWorkerTweaksSubscribeResponse({
|
||||||
|
required super.result,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(method: ElectrumRequestMethods.tweaksSubscribe.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> resultJson(result) {
|
||||||
|
return result.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerTweaksSubscribeResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerTweaksSubscribeResponse(
|
||||||
|
result: TweaksSyncResponse.fromJson(json['result'] as Map<String, dynamic>),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
52
cw_bitcoin/lib/electrum_worker/methods/version.dart
Normal file
52
cw_bitcoin/lib/electrum_worker/methods/version.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerGetVersionRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerGetVersionRequest({this.id});
|
||||||
|
|
||||||
|
final int? id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.version.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetVersionRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetVersionRequest(id: json['id'] as int?);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetVersionError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerGetVersionError({
|
||||||
|
required super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get method => ElectrumRequestMethods.version.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<List<String>, List<String>> {
|
||||||
|
ElectrumWorkerGetVersionResponse({
|
||||||
|
required super.result,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
}) : super(method: ElectrumRequestMethods.version.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> resultJson(result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetVersionResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetVersionResponse(
|
||||||
|
result: json['result'] as List<String>,
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.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:cw_core/hardware/hardware_account_data.dart';
|
||||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||||
import 'package:ledger_litecoin/ledger_litecoin.dart';
|
import 'package:ledger_litecoin/ledger_litecoin.dart';
|
||||||
|
@ -12,8 +11,7 @@ class LitecoinHardwareWalletService {
|
||||||
|
|
||||||
final LedgerConnection ledgerConnection;
|
final LedgerConnection ledgerConnection;
|
||||||
|
|
||||||
Future<List<HardwareAccountData>> getAvailableAccounts(
|
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
|
||||||
{int index = 0, int limit = 5}) async {
|
|
||||||
final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
|
final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
|
||||||
|
|
||||||
await litecoinLedgerApp.getVersion();
|
await litecoinLedgerApp.getVersion();
|
||||||
|
@ -27,14 +25,18 @@ class LitecoinHardwareWalletService {
|
||||||
final xpub = await litecoinLedgerApp.getXPubKey(
|
final xpub = await litecoinLedgerApp.getXPubKey(
|
||||||
accountsDerivationPath: derivationPath,
|
accountsDerivationPath: derivationPath,
|
||||||
xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
|
xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
|
||||||
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion)
|
final bip32 =
|
||||||
.childKey(Bip32KeyIndex(0));
|
Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0));
|
||||||
|
|
||||||
final address = generateP2WPKHAddress(
|
final address = P2wpkhAddress.fromDerivation(
|
||||||
hd: hd, index: 0, network: LitecoinNetwork.mainnet);
|
bip32: bip32,
|
||||||
|
derivationInfo: BitcoinDerivationInfos.LITECOIN,
|
||||||
|
isChange: false,
|
||||||
|
index: 0,
|
||||||
|
);
|
||||||
|
|
||||||
accounts.add(HardwareAccountData(
|
accounts.add(HardwareAccountData(
|
||||||
address: address,
|
address: address.toAddress(LitecoinNetwork.mainnet),
|
||||||
accountIndex: i,
|
accountIndex: i,
|
||||||
derivationPath: derivationPath,
|
derivationPath: derivationPath,
|
||||||
xpub: xpub,
|
xpub: xpub,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:convert/convert.dart' as convert;
|
import 'package:convert/convert.dart' as convert;
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
|
// import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_core/cake_hive.dart';
|
import 'package:cw_core/cake_hive.dart';
|
||||||
import 'package:cw_core/mweb_utxo.dart';
|
import 'package:cw_core/mweb_utxo.dart';
|
||||||
import 'package:cw_mweb/mwebd.pbgrpc.dart';
|
import 'package:cw_mweb/mwebd.pbgrpc.dart';
|
||||||
|
@ -21,8 +21,6 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/pending_transaction.dart';
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
@ -70,6 +68,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
Map<String, int>? initialChangeAddressIndex,
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
int? initialMwebHeight,
|
int? initialMwebHeight,
|
||||||
bool? alwaysScan,
|
bool? alwaysScan,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
}) : super(
|
}) : super(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
|
@ -83,10 +82,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
encryptionFileUtils: encryptionFileUtils,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
currency: CryptoCurrency.ltc,
|
currency: CryptoCurrency.ltc,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
) {
|
) {
|
||||||
if (seedBytes != null) {
|
if (seedBytes != null) {
|
||||||
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
|
mwebHd =
|
||||||
"m/1000'") as Bip32Slip10Secp256k1;
|
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
|
||||||
mwebEnabled = alwaysScan ?? false;
|
mwebEnabled = alwaysScan ?? false;
|
||||||
} else {
|
} else {
|
||||||
mwebHd = null;
|
mwebHd = null;
|
||||||
|
@ -98,38 +98,24 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
initialMwebAddresses: initialMwebAddresses,
|
initialMwebAddresses: initialMwebAddresses,
|
||||||
mainHd: hd,
|
|
||||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
|
||||||
network: network,
|
network: network,
|
||||||
mwebHd: mwebHd,
|
mwebHd: mwebHd,
|
||||||
mwebEnabled: mwebEnabled,
|
mwebEnabled: mwebEnabled,
|
||||||
isHardwareWallet: walletInfo.isHardwareWallet,
|
isHardwareWallet: walletInfo.isHardwareWallet,
|
||||||
|
hdWallets: hdWallets,
|
||||||
);
|
);
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||||
});
|
});
|
||||||
reaction((_) => mwebSyncStatus, (status) async {
|
reaction((_) => mwebSyncStatus, (status) async {
|
||||||
if (mwebSyncStatus is FailedSyncStatus) {
|
if (mwebSyncStatus is FailedSyncStatus) {
|
||||||
// we failed to connect to mweb, check if we are connected to the litecoin node:
|
await CwMweb.stop();
|
||||||
late int nodeHeight;
|
await Future.delayed(const Duration(seconds: 5));
|
||||||
try {
|
startSync();
|
||||||
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) {
|
} else if (mwebSyncStatus is SyncingSyncStatus) {
|
||||||
syncStatus = mwebSyncStatus;
|
syncStatus = mwebSyncStatus;
|
||||||
} else if (mwebSyncStatus is SyncronizingSyncStatus) {
|
} else if (mwebSyncStatus is SynchronizingSyncStatus) {
|
||||||
if (syncStatus is! SyncronizingSyncStatus) {
|
if (syncStatus is! SynchronizingSyncStatus) {
|
||||||
syncStatus = mwebSyncStatus;
|
syncStatus = mwebSyncStatus;
|
||||||
}
|
}
|
||||||
} else if (mwebSyncStatus is SyncedSyncStatus) {
|
} else if (mwebSyncStatus is SyncedSyncStatus) {
|
||||||
|
@ -154,20 +140,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
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 spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
|
List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
|
||||||
|
|
||||||
static Future<LitecoinWallet> create(
|
static Future<LitecoinWallet> create({
|
||||||
{required String mnemonic,
|
required String mnemonic,
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
String? addressPageType,
|
String? addressPageType,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
List<BitcoinAddressRecord>? initialMwebAddresses,
|
List<BitcoinAddressRecord>? initialMwebAddresses,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
Map<String, int>? initialRegularAddressIndex,
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
Map<String, int>? initialChangeAddressIndex}) async {
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
|
}) async {
|
||||||
late Uint8List seedBytes;
|
late Uint8List seedBytes;
|
||||||
|
late BitcoinDerivationType derivationType;
|
||||||
|
|
||||||
switch (walletInfo.derivationInfo?.derivationType) {
|
switch (walletInfo.derivationInfo?.derivationType) {
|
||||||
case DerivationType.bip39:
|
case DerivationType.bip39:
|
||||||
|
@ -175,10 +164,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
mnemonic,
|
mnemonic,
|
||||||
passphrase: passphrase ?? "",
|
passphrase: passphrase ?? "",
|
||||||
);
|
);
|
||||||
|
derivationType = BitcoinDerivationType.bip39;
|
||||||
break;
|
break;
|
||||||
case DerivationType.electrum:
|
case DerivationType.electrum:
|
||||||
default:
|
default:
|
||||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
||||||
|
derivationType = BitcoinDerivationType.electrum;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return LitecoinWallet(
|
return LitecoinWallet(
|
||||||
|
@ -195,6 +186,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
addressPageType: addressPageType,
|
addressPageType: addressPageType,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,6 +196,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
required bool alwaysScan,
|
required bool alwaysScan,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
}) async {
|
}) async {
|
||||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
|
@ -239,10 +232,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
walletInfo.derivationInfo ??= DerivationInfo();
|
walletInfo.derivationInfo ??= DerivationInfo();
|
||||||
|
|
||||||
// set the default if not present:
|
// set the default if not present:
|
||||||
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
|
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? ELECTRUM_PATH;
|
||||||
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
|
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
|
||||||
|
|
||||||
Uint8List? seedBytes = null;
|
Uint8List? seedBytes = null;
|
||||||
|
late BitcoinDerivationType derivationType;
|
||||||
final mnemonic = keysData.mnemonic;
|
final mnemonic = keysData.mnemonic;
|
||||||
final passphrase = keysData.passphrase;
|
final passphrase = keysData.passphrase;
|
||||||
|
|
||||||
|
@ -253,10 +247,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
mnemonic,
|
mnemonic,
|
||||||
passphrase: passphrase ?? "",
|
passphrase: passphrase ?? "",
|
||||||
);
|
);
|
||||||
|
derivationType = BitcoinDerivationType.bip39;
|
||||||
break;
|
break;
|
||||||
case DerivationType.electrum:
|
case DerivationType.electrum:
|
||||||
default:
|
default:
|
||||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
||||||
|
derivationType = BitcoinDerivationType.electrum;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,6 +273,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||||
addressPageType: snp?.addressPageType,
|
addressPageType: snp?.addressPageType,
|
||||||
alwaysScan: snp?.alwaysScan,
|
alwaysScan: snp?.alwaysScan,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,16 +298,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mwebSyncStatus is SyncronizingSyncStatus) {
|
if (mwebSyncStatus is SynchronizingSyncStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
|
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
|
||||||
_syncTimer?.cancel();
|
_syncTimer?.cancel();
|
||||||
try {
|
try {
|
||||||
mwebSyncStatus = SyncronizingSyncStatus();
|
mwebSyncStatus = SynchronizingSyncStatus();
|
||||||
try {
|
try {
|
||||||
await subscribeForUpdates();
|
await subscribeForUpdates([]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("failed to subcribe for updates: $e");
|
print("failed to subcribe for updates: $e");
|
||||||
}
|
}
|
||||||
|
@ -338,8 +335,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final nodeHeight =
|
final nodeHeight = await currentChainTip ?? 0;
|
||||||
await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node
|
|
||||||
|
|
||||||
if (nodeHeight == 0) {
|
if (nodeHeight == 0) {
|
||||||
// we aren't connected to the ltc node yet
|
// we aren't connected to the ltc node yet
|
||||||
|
@ -430,13 +426,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@override
|
@override
|
||||||
Future<void> rescan({
|
Future<void> rescan({required int height}) async {
|
||||||
required int height,
|
|
||||||
int? chainTip,
|
|
||||||
ScanData? scanData,
|
|
||||||
bool? doSingleScan,
|
|
||||||
bool? usingElectrs,
|
|
||||||
}) async {
|
|
||||||
_syncTimer?.cancel();
|
_syncTimer?.cancel();
|
||||||
await walletInfo.updateRestoreHeight(height);
|
await walletInfo.updateRestoreHeight(height);
|
||||||
|
|
||||||
|
@ -559,8 +549,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
_utxoStream = responseStream.listen((Utxo sUtxo) async {
|
_utxoStream = responseStream.listen((Utxo sUtxo) async {
|
||||||
// we're processing utxos, so our balance could still be innacurate:
|
// we're processing utxos, so our balance could still be innacurate:
|
||||||
if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
|
if (mwebSyncStatus is! SynchronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
|
||||||
mwebSyncStatus = SyncronizingSyncStatus();
|
mwebSyncStatus = SynchronizingSyncStatus();
|
||||||
processingUtxos = true;
|
processingUtxos = true;
|
||||||
_processingTimer?.cancel();
|
_processingTimer?.cancel();
|
||||||
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
|
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
|
||||||
|
@ -631,7 +621,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
final status = await CwMweb.status(StatusRequest());
|
final status = await CwMweb.status(StatusRequest());
|
||||||
final height = await electrumClient.getCurrentBlockChainTip();
|
final height = await currentChainTip;
|
||||||
if (height == null || status.blockHeaderHeight != height) return;
|
if (height == null || status.blockHeaderHeight != height) return;
|
||||||
if (status.mwebUtxosHeight != height) return; // we aren't synced
|
if (status.mwebUtxosHeight != height) return; // we aren't synced
|
||||||
int amount = 0;
|
int amount = 0;
|
||||||
|
@ -766,7 +756,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
});
|
});
|
||||||
|
|
||||||
// copy coin control attributes to mwebCoins:
|
// copy coin control attributes to mwebCoins:
|
||||||
await updateCoins(mwebUnspentCoins);
|
// await updateCoins(mwebUnspentCoins);
|
||||||
// get regular ltc unspents (this resets unspentCoins):
|
// get regular ltc unspents (this resets unspentCoins):
|
||||||
await super.updateAllUnspents();
|
await super.updateAllUnspents();
|
||||||
// add the mwebCoins:
|
// add the mwebCoins:
|
||||||
|
@ -774,94 +764,126 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ElectrumBalance> fetchBalances() async {
|
@action
|
||||||
final balance = await super.fetchBalances();
|
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||||
if (!mwebEnabled) {
|
throw UnimplementedError();
|
||||||
return balance;
|
// try {
|
||||||
}
|
// final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||||
|
|
||||||
// update unspent balances:
|
// await Future.wait(LITECOIN_ADDRESS_TYPES
|
||||||
await updateUnspent();
|
// .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
|
||||||
|
|
||||||
int confirmed = balance.confirmed;
|
// return historiesWithDetails;
|
||||||
int unconfirmed = balance.unconfirmed;
|
// } catch (e) {
|
||||||
int confirmedMweb = 0;
|
// print("fetchTransactions $e");
|
||||||
int unconfirmedMweb = 0;
|
// return {};
|
||||||
try {
|
// }
|
||||||
mwebUtxosBox.values.forEach((utxo) {
|
|
||||||
if (utxo.height > 0) {
|
|
||||||
confirmedMweb += utxo.value.toInt();
|
|
||||||
} else {
|
|
||||||
unconfirmedMweb += utxo.value.toInt();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (unconfirmedMweb > 0) {
|
|
||||||
unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
for (var addressRecord in walletAddresses.allAddresses) {
|
|
||||||
addressRecord.balance = 0;
|
|
||||||
addressRecord.txCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
unspentCoins.forEach((coin) {
|
|
||||||
final coinInfoList = unspentCoinsInfo.values.where(
|
|
||||||
(element) =>
|
|
||||||
element.walletId.contains(id) &&
|
|
||||||
element.hash.contains(coin.hash) &&
|
|
||||||
element.vout == coin.vout,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (coinInfoList.isNotEmpty) {
|
|
||||||
final coinInfo = coinInfoList.first;
|
|
||||||
|
|
||||||
coin.isFrozen = coinInfo.isFrozen;
|
|
||||||
coin.isSending = coinInfo.isSending;
|
|
||||||
coin.note = coinInfo.note;
|
|
||||||
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
|
||||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
|
||||||
} else {
|
|
||||||
super.addCoinInfo(coin);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// update the txCount for each address using the tx history, since we can't rely on mwebd
|
|
||||||
// to have an accurate count, we should just keep it in sync with what we know from the tx history:
|
|
||||||
for (final tx in transactionHistory.transactions.values) {
|
|
||||||
// if (tx.isPending) continue;
|
|
||||||
if (tx.inputAddresses == null || tx.outputAddresses == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final txAddresses = tx.inputAddresses! + tx.outputAddresses!;
|
|
||||||
for (final address in txAddresses) {
|
|
||||||
final addressRecord = walletAddresses.allAddresses
|
|
||||||
.firstWhereOrNull((addressRecord) => addressRecord.address == address);
|
|
||||||
if (addressRecord == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
addressRecord.txCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ElectrumBalance(
|
|
||||||
confirmed: confirmed,
|
|
||||||
unconfirmed: unconfirmed,
|
|
||||||
frozen: balance.frozen,
|
|
||||||
secondConfirmed: confirmedMweb,
|
|
||||||
secondUnconfirmed: unconfirmedMweb,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// @action
|
||||||
|
// Future<void> subscribeForUpdates([
|
||||||
|
// Iterable<BitcoinAddressRecord>? unsubscribedScriptHashes,
|
||||||
|
// ]) async {
|
||||||
|
// final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
|
||||||
|
// (address) =>
|
||||||
|
// !scripthashesListening.contains(address.scriptHash) &&
|
||||||
|
// address.type != SegwitAddresType.mweb,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return super.subscribeForUpdates(unsubscribedScriptHashes);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<ElectrumBalance> fetchBalances() async {
|
||||||
|
// final balance = await super.fetchBalances();
|
||||||
|
|
||||||
|
// if (!mwebEnabled) {
|
||||||
|
// return balance;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // update unspent balances:
|
||||||
|
// await updateUnspent();
|
||||||
|
|
||||||
|
// int confirmed = balance.confirmed;
|
||||||
|
// int unconfirmed = balance.unconfirmed;
|
||||||
|
// int confirmedMweb = 0;
|
||||||
|
// int unconfirmedMweb = 0;
|
||||||
|
// try {
|
||||||
|
// mwebUtxosBox.values.forEach((utxo) {
|
||||||
|
// if (utxo.height > 0) {
|
||||||
|
// confirmedMweb += utxo.value.toInt();
|
||||||
|
// } else {
|
||||||
|
// unconfirmedMweb += utxo.value.toInt();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// if (unconfirmedMweb > 0) {
|
||||||
|
// unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
|
||||||
|
// }
|
||||||
|
// } catch (_) {}
|
||||||
|
|
||||||
|
// for (var addressRecord in walletAddresses.allAddresses) {
|
||||||
|
// addressRecord.balance = 0;
|
||||||
|
// addressRecord.txCount = 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// unspentCoins.forEach((coin) {
|
||||||
|
// final coinInfoList = unspentCoinsInfo.values.where(
|
||||||
|
// (element) =>
|
||||||
|
// element.walletId.contains(id) &&
|
||||||
|
// element.hash.contains(coin.hash) &&
|
||||||
|
// element.vout == coin.vout,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (coinInfoList.isNotEmpty) {
|
||||||
|
// final coinInfo = coinInfoList.first;
|
||||||
|
|
||||||
|
// coin.isFrozen = coinInfo.isFrozen;
|
||||||
|
// coin.isSending = coinInfo.isSending;
|
||||||
|
// coin.note = coinInfo.note;
|
||||||
|
// if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
||||||
|
// coin.bitcoinAddressRecord.balance += coinInfo.value;
|
||||||
|
// } else {
|
||||||
|
// super.addCoinInfo(coin);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // update the txCount for each address using the tx history, since we can't rely on mwebd
|
||||||
|
// // to have an accurate count, we should just keep it in sync with what we know from the tx history:
|
||||||
|
// for (final tx in transactionHistory.transactions.values) {
|
||||||
|
// // if (tx.isPending) continue;
|
||||||
|
// if (tx.inputAddresses == null || tx.outputAddresses == null) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// final txAddresses = tx.inputAddresses! + tx.outputAddresses!;
|
||||||
|
// for (final address in txAddresses) {
|
||||||
|
// final addressRecord = walletAddresses.allAddresses
|
||||||
|
// .firstWhereOrNull((addressRecord) => addressRecord.address == address);
|
||||||
|
// if (addressRecord == null) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// addressRecord.txCount++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return ElectrumBalance(
|
||||||
|
// confirmed: confirmed,
|
||||||
|
// unconfirmed: unconfirmed,
|
||||||
|
// frozen: balance.frozen,
|
||||||
|
// secondConfirmed: confirmedMweb,
|
||||||
|
// secondUnconfirmed: unconfirmedMweb,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int feeRate(TransactionPriority priority) {
|
int feeRate(TransactionPriority priority) {
|
||||||
if (priority is LitecoinTransactionPriority) {
|
if (priority is ElectrumTransactionPriority) {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case LitecoinTransactionPriority.slow:
|
case ElectrumTransactionPriority.slow:
|
||||||
return 1;
|
return 1;
|
||||||
case LitecoinTransactionPriority.medium:
|
case ElectrumTransactionPriority.medium:
|
||||||
return 2;
|
return 2;
|
||||||
case LitecoinTransactionPriority.fast:
|
case ElectrumTransactionPriority.fast:
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -873,25 +895,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
Future<int> calcFee({
|
Future<int> calcFee({
|
||||||
required List<UtxoWithAddress> utxos,
|
required List<UtxoWithAddress> utxos,
|
||||||
required List<BitcoinBaseOutput> outputs,
|
required List<BitcoinBaseOutput> outputs,
|
||||||
required BasedUtxoNetwork network,
|
|
||||||
String? memo,
|
String? memo,
|
||||||
required int feeRate,
|
required int feeRate,
|
||||||
List<ECPrivateInfo>? inputPrivKeyInfos,
|
|
||||||
List<Outpoint>? vinOutpoints,
|
|
||||||
}) async {
|
}) async {
|
||||||
final spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddresType.mweb);
|
final spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddresType.mweb);
|
||||||
final paysToMweb = outputs
|
final paysToMweb = outputs
|
||||||
.any((output) => output.toOutput.scriptPubKey.getAddressType() == SegwitAddresType.mweb);
|
.any((output) => output.toOutput.scriptPubKey.getAddressType() == SegwitAddresType.mweb);
|
||||||
if (!spendsMweb && !paysToMweb) {
|
if (!spendsMweb && !paysToMweb) {
|
||||||
return await super.calcFee(
|
return await super.calcFee(utxos: utxos, outputs: outputs, memo: memo, feeRate: feeRate);
|
||||||
utxos: utxos,
|
|
||||||
outputs: outputs,
|
|
||||||
network: network,
|
|
||||||
memo: memo,
|
|
||||||
feeRate: feeRate,
|
|
||||||
inputPrivKeyInfos: inputPrivKeyInfos,
|
|
||||||
vinOutpoints: vinOutpoints,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mwebEnabled) {
|
if (!mwebEnabled) {
|
||||||
|
@ -901,7 +912,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
if (outputs.length == 1 && outputs[0].toOutput.amount == BigInt.zero) {
|
if (outputs.length == 1 && outputs[0].toOutput.amount == BigInt.zero) {
|
||||||
outputs = [
|
outputs = [
|
||||||
BitcoinScriptOutput(
|
BitcoinScriptOutput(
|
||||||
script: outputs[0].toOutput.scriptPubKey, value: utxos.sumOfUtxosValue())
|
script: outputs[0].toOutput.scriptPubKey,
|
||||||
|
value: utxos.sumOfUtxosValue(),
|
||||||
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -928,14 +941,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
var feeIncrease = posOutputSum - expectedPegin;
|
var feeIncrease = posOutputSum - expectedPegin;
|
||||||
if (expectedPegin > 0 && fee == BigInt.zero) {
|
if (expectedPegin > 0 && fee == BigInt.zero) {
|
||||||
feeIncrease += await super.calcFee(
|
feeIncrease += await super.calcFee(
|
||||||
utxos: posUtxos,
|
utxos: posUtxos,
|
||||||
outputs: tx.outputs
|
outputs: tx.outputs
|
||||||
.map((output) =>
|
.map((output) =>
|
||||||
BitcoinScriptOutput(script: output.scriptPubKey, value: output.amount))
|
BitcoinScriptOutput(script: output.scriptPubKey, value: output.amount))
|
||||||
.toList(),
|
.toList(),
|
||||||
network: network,
|
memo: memo,
|
||||||
memo: memo,
|
feeRate: feeRate,
|
||||||
feeRate: feeRate) +
|
) +
|
||||||
feeRate * 41;
|
feeRate * 41;
|
||||||
}
|
}
|
||||||
return fee.toInt() + feeIncrease;
|
return fee.toInt() + feeIncrease;
|
||||||
|
@ -949,8 +962,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
if (!mwebEnabled) {
|
if (!mwebEnabled) {
|
||||||
tx.changeAddressOverride =
|
tx.changeAddressOverride =
|
||||||
(await (walletAddresses as LitecoinWalletAddresses)
|
(await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false))
|
||||||
.getChangeAddress(isPegIn: false))
|
|
||||||
.address;
|
.address;
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
@ -990,10 +1002,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
bool isPegIn = !hasMwebInput && hasMwebOutput;
|
bool isPegIn = !hasMwebInput && hasMwebOutput;
|
||||||
bool isRegular = !hasMwebInput && !hasMwebOutput;
|
bool isRegular = !hasMwebInput && !hasMwebOutput;
|
||||||
tx.changeAddressOverride =
|
tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
|
||||||
(await (walletAddresses as LitecoinWalletAddresses)
|
.getChangeAddress(isPegIn: isPegIn || isRegular))
|
||||||
.getChangeAddress(isPegIn: isPegIn || isRegular))
|
.address;
|
||||||
.address;
|
|
||||||
if (!hasMwebInput && !hasMwebOutput) {
|
if (!hasMwebInput && !hasMwebOutput) {
|
||||||
tx.isMweb = false;
|
tx.isMweb = false;
|
||||||
return tx;
|
return tx;
|
||||||
|
@ -1025,12 +1036,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
witnesses: tx2.inputs.asMap().entries.map((e) {
|
witnesses: tx2.inputs.asMap().entries.map((e) {
|
||||||
final utxo = unspentCoins
|
final utxo = unspentCoins
|
||||||
.firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex);
|
.firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex);
|
||||||
final key = generateECPrivate(
|
final addressRecord = (utxo.bitcoinAddressRecord as BitcoinAddressRecord);
|
||||||
hd: utxo.bitcoinAddressRecord.isHidden
|
final path = addressRecord.derivationInfo.derivationPath
|
||||||
? walletAddresses.sideHd
|
.addElem(
|
||||||
: walletAddresses.mainHd,
|
Bip32KeyIndex(BitcoinAddressUtils.getAccountFromChange(addressRecord.isChange)))
|
||||||
index: utxo.bitcoinAddressRecord.index,
|
.addElem(Bip32KeyIndex(addressRecord.index));
|
||||||
network: network);
|
final key = ECPrivate.fromBip32(bip32: bip32.derive(path));
|
||||||
final digest = tx2.getTransactionSegwitDigit(
|
final digest = tx2.getTransactionSegwitDigit(
|
||||||
txInIndex: e.key,
|
txInIndex: e.key,
|
||||||
script: key.getPublic().toP2pkhAddress().toScriptPubKey(),
|
script: key.getPublic().toP2pkhAddress().toScriptPubKey(),
|
||||||
|
@ -1113,10 +1124,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> signMessage(String message, {String? address = null}) async {
|
Future<String> signMessage(String message, {String? address = null}) async {
|
||||||
final index = address != null
|
Bip32Slip10Secp256k1 HD = bip32;
|
||||||
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
|
|
||||||
: null;
|
final record = walletAddresses.allAddresses.firstWhere((element) => element.address == address);
|
||||||
final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
|
|
||||||
|
if (record.isChange) {
|
||||||
|
HD = HD.childKey(Bip32KeyIndex(1));
|
||||||
|
} else {
|
||||||
|
HD = HD.childKey(Bip32KeyIndex(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
HD = HD.childKey(Bip32KeyIndex(record.index));
|
||||||
final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex());
|
final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex());
|
||||||
|
|
||||||
final privateKey = ECDSAPrivateKey.fromBytes(
|
final privateKey = ECDSAPrivateKey.fromBytes(
|
||||||
|
@ -1240,8 +1258,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
@override
|
@override
|
||||||
void setLedgerConnection(LedgerConnection connection) {
|
void setLedgerConnection(LedgerConnection connection) {
|
||||||
_ledgerConnection = connection;
|
_ledgerConnection = connection;
|
||||||
_litecoinLedgerApp =
|
_litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
|
||||||
LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1258,7 +1276,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
}) async {
|
}) async {
|
||||||
final readyInputs = <LedgerTransaction>[];
|
final readyInputs = <LedgerTransaction>[];
|
||||||
for (final utxo in utxos) {
|
for (final utxo in utxos) {
|
||||||
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
|
final rawTx =
|
||||||
|
(await getTransactionExpanded(hash: utxo.utxo.txHash)).originalTransaction.toHex();
|
||||||
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
||||||
|
|
||||||
readyInputs.add(LedgerTransaction(
|
readyInputs.add(LedgerTransaction(
|
||||||
|
@ -1277,19 +1296,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
|
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final rawHex = await _litecoinLedgerApp!.createTransaction(
|
final rawHex = await _litecoinLedgerApp!.createTransaction(
|
||||||
inputs: readyInputs,
|
inputs: readyInputs,
|
||||||
outputs: outputs
|
outputs: outputs
|
||||||
.map((e) => TransactionOutput.fromBigInt(
|
.map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
|
||||||
(e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
|
Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
|
||||||
.toList(),
|
.toList(),
|
||||||
changePath: changePath,
|
changePath: changePath,
|
||||||
sigHashType: 0x01,
|
sigHashType: 0x01,
|
||||||
additionals: ["bech32"],
|
additionals: ["bech32"],
|
||||||
isSegWit: true,
|
isSegWit: true,
|
||||||
useTrustedInputForSegwit: true
|
useTrustedInputForSegwit: true);
|
||||||
);
|
|
||||||
|
|
||||||
return BtcTransaction.fromRaw(rawHex);
|
return BtcTransaction.fromRaw(rawHex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,6 @@ import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_unspent.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';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_mweb/cw_mweb.dart';
|
import 'package:cw_mweb/cw_mweb.dart';
|
||||||
|
@ -16,19 +14,16 @@ import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'litecoin_wallet_addresses.g.dart';
|
part 'litecoin_wallet_addresses.g.dart';
|
||||||
|
|
||||||
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
|
class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
|
||||||
with _$LitecoinWalletAddresses;
|
|
||||||
|
|
||||||
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||||
with Store {
|
|
||||||
LitecoinWalletAddressesBase(
|
LitecoinWalletAddressesBase(
|
||||||
WalletInfo walletInfo, {
|
WalletInfo walletInfo, {
|
||||||
required super.mainHd,
|
|
||||||
required super.sideHd,
|
|
||||||
required super.network,
|
required super.network,
|
||||||
required super.isHardwareWallet,
|
required super.isHardwareWallet,
|
||||||
required this.mwebHd,
|
required this.mwebHd,
|
||||||
required this.mwebEnabled,
|
required this.mwebEnabled,
|
||||||
|
required super.hdWallets,
|
||||||
super.initialAddresses,
|
super.initialAddresses,
|
||||||
super.initialMwebAddresses,
|
super.initialMwebAddresses,
|
||||||
super.initialRegularAddressIndex,
|
super.initialRegularAddressIndex,
|
||||||
|
@ -46,14 +41,13 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||||
List<String> mwebAddrs = [];
|
List<String> mwebAddrs = [];
|
||||||
bool generating = false;
|
bool generating = false;
|
||||||
|
|
||||||
List<int> get scanSecret =>
|
List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||||
mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
|
||||||
List<int> get spendPubkey =>
|
List<int> get spendPubkey =>
|
||||||
mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
|
mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (!isHardwareWallet) await initMwebAddresses();
|
if (!super.isHardwareWallet) await initMwebAddresses();
|
||||||
await super.init();
|
await super.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,12 +98,16 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||||
List<BitcoinAddressRecord> addressRecords = mwebAddrs
|
List<BitcoinAddressRecord> addressRecords = mwebAddrs
|
||||||
.asMap()
|
.asMap()
|
||||||
.entries
|
.entries
|
||||||
.map((e) => BitcoinAddressRecord(
|
.map(
|
||||||
e.value,
|
(e) => BitcoinAddressRecord(
|
||||||
index: e.key,
|
e.value,
|
||||||
type: SegwitAddresType.mweb,
|
index: e.key,
|
||||||
network: network,
|
type: SegwitAddresType.mweb,
|
||||||
))
|
network: network,
|
||||||
|
derivationInfo: BitcoinAddressUtils.getDerivationFromType(SegwitAddresType.p2wpkh),
|
||||||
|
derivationType: CWBitcoinDerivationType.bip39,
|
||||||
|
),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
addMwebAddresses(addressRecords);
|
addMwebAddresses(addressRecords);
|
||||||
print("set ${addressRecords.length} mweb addresses");
|
print("set ${addressRecords.length} mweb addresses");
|
||||||
|
@ -121,30 +119,47 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||||
await ensureMwebAddressUpToIndexExists(20);
|
await ensureMwebAddressUpToIndexExists(20);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress({
|
BitcoinBaseAddress generateAddress({
|
||||||
required int index,
|
required CWBitcoinDerivationType derivationType,
|
||||||
required Bip32Slip10Secp256k1 hd,
|
required bool isChange,
|
||||||
BitcoinAddressType? addressType,
|
required int index,
|
||||||
}) {
|
required BitcoinAddressType addressType,
|
||||||
if (addressType == SegwitAddresType.mweb) {
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1];
|
}) {
|
||||||
|
if (addressType == SegwitAddresType.mweb) {
|
||||||
|
return MwebAddress.fromAddress(address: mwebAddrs[0], network: network);
|
||||||
|
}
|
||||||
|
|
||||||
|
return P2wpkhAddress.fromDerivation(
|
||||||
|
bip32: bip32,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return generateP2WPKHAddress(hd: hd, index: index, network: network);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getAddressAsync({
|
Future<String> getAddressAsync({
|
||||||
|
required CWBitcoinDerivationType derivationType,
|
||||||
|
required bool isChange,
|
||||||
required int index,
|
required int index,
|
||||||
required Bip32Slip10Secp256k1 hd,
|
required BitcoinAddressType addressType,
|
||||||
BitcoinAddressType? addressType,
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
}) async {
|
}) async {
|
||||||
if (addressType == SegwitAddresType.mweb) {
|
if (addressType == SegwitAddresType.mweb) {
|
||||||
await ensureMwebAddressUpToIndexExists(index);
|
await ensureMwebAddressUpToIndexExists(index);
|
||||||
}
|
}
|
||||||
return getAddress(index: index, hd: hd, addressType: addressType);
|
|
||||||
|
return getAddress(
|
||||||
|
derivationType: derivationType,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
addressType: addressType,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -198,6 +213,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||||
index: 0,
|
index: 0,
|
||||||
type: SegwitAddresType.mweb,
|
type: SegwitAddresType.mweb,
|
||||||
network: network,
|
network: network,
|
||||||
|
derivationInfo: BitcoinAddressUtils.getDerivationFromType(SegwitAddresType.p2wpkh),
|
||||||
|
derivationType: CWBitcoinDerivationType.bip39,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,18 @@ class LitecoinWalletService extends WalletService<
|
||||||
BitcoinRestoreWalletFromWIFCredentials,
|
BitcoinRestoreWalletFromWIFCredentials,
|
||||||
BitcoinRestoreWalletFromHardware> {
|
BitcoinRestoreWalletFromHardware> {
|
||||||
LitecoinWalletService(
|
LitecoinWalletService(
|
||||||
this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
|
this.walletInfoSource,
|
||||||
|
this.unspentCoinsInfoSource,
|
||||||
|
this.alwaysScan,
|
||||||
|
this.isDirect,
|
||||||
|
this.mempoolAPIEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
final bool alwaysScan;
|
final bool alwaysScan;
|
||||||
final bool isDirect;
|
final bool isDirect;
|
||||||
|
final bool mempoolAPIEnabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletType getType() => WalletType.litecoin;
|
WalletType getType() => WalletType.litecoin;
|
||||||
|
@ -55,6 +61,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -68,7 +75,6 @@ class LitecoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> openWallet(String name, String password) async {
|
Future<LitecoinWallet> openWallet(String name, String password) async {
|
||||||
|
|
||||||
final walletInfo = walletInfoSource.values
|
final walletInfo = walletInfoSource.values
|
||||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||||
|
|
||||||
|
@ -80,6 +86,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
saveBackup(name);
|
saveBackup(name);
|
||||||
|
@ -93,6 +100,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -135,6 +143,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
await currentWallet.renameWalletFiles(newName);
|
await currentWallet.renameWalletFiles(newName);
|
||||||
|
@ -161,6 +170,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -186,6 +196,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
import 'package:cw_bitcoin/exceptions.dart';
|
import 'package:cw_bitcoin/exceptions.dart';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_core/pending_transaction.dart';
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_bitcoin/electrum.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
@ -15,11 +15,10 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
PendingBitcoinTransaction(
|
PendingBitcoinTransaction(
|
||||||
this._tx,
|
this._tx,
|
||||||
this.type, {
|
this.type, {
|
||||||
required this.electrumClient,
|
required this.sendWorker,
|
||||||
required this.amount,
|
required this.amount,
|
||||||
required this.fee,
|
required this.fee,
|
||||||
required this.feeRate,
|
required this.feeRate,
|
||||||
this.network,
|
|
||||||
required this.hasChange,
|
required this.hasChange,
|
||||||
this.isSendAll = false,
|
this.isSendAll = false,
|
||||||
this.hasTaprootInputs = false,
|
this.hasTaprootInputs = false,
|
||||||
|
@ -29,11 +28,10 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
|
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
final BtcTransaction _tx;
|
final BtcTransaction _tx;
|
||||||
final ElectrumClient electrumClient;
|
Future<dynamic> Function(ElectrumWorkerRequest) sendWorker;
|
||||||
final int amount;
|
final int amount;
|
||||||
final int fee;
|
final int fee;
|
||||||
final String feeRate;
|
final String feeRate;
|
||||||
final BasedUtxoNetwork? network;
|
|
||||||
final bool isSendAll;
|
final bool isSendAll;
|
||||||
final bool hasChange;
|
final bool hasChange;
|
||||||
final bool hasTaprootInputs;
|
final bool hasTaprootInputs;
|
||||||
|
@ -51,10 +49,10 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
String get hex => hexOverride ?? _tx.serialize();
|
String get hex => hexOverride ?? _tx.serialize();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
String get amountFormatted => BitcoinAmountUtils.bitcoinAmountToString(amount: amount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
String get feeFormatted => BitcoinAmountUtils.bitcoinAmountToString(amount: fee);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int? get outputCount => _tx.outputs.length;
|
int? get outputCount => _tx.outputs.length;
|
||||||
|
@ -80,40 +78,39 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
Future<void> _commit() async {
|
Future<void> _commit() async {
|
||||||
int? callId;
|
int? callId;
|
||||||
|
|
||||||
final result = await electrumClient.broadcastTransaction(
|
final result = await sendWorker(ElectrumWorkerBroadcastRequest(transactionRaw: hex)) as String;
|
||||||
transactionRaw: hex, network: network, idCallback: (id) => callId = id);
|
|
||||||
|
|
||||||
if (result.isEmpty) {
|
// if (result.isEmpty) {
|
||||||
if (callId != null) {
|
// if (callId != null) {
|
||||||
final error = electrumClient.getErrorMessage(callId!);
|
// final error = sendWorker(getErrorMessage(callId!));
|
||||||
|
|
||||||
if (error.contains("dust")) {
|
// if (error.contains("dust")) {
|
||||||
if (hasChange) {
|
// if (hasChange) {
|
||||||
throw BitcoinTransactionCommitFailedDustChange();
|
// throw BitcoinTransactionCommitFailedDustChange();
|
||||||
} else if (!isSendAll) {
|
// } else if (!isSendAll) {
|
||||||
throw BitcoinTransactionCommitFailedDustOutput();
|
// throw BitcoinTransactionCommitFailedDustOutput();
|
||||||
} else {
|
// } else {
|
||||||
throw BitcoinTransactionCommitFailedDustOutputSendAll();
|
// throw BitcoinTransactionCommitFailedDustOutputSendAll();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (error.contains("bad-txns-vout-negative")) {
|
// if (error.contains("bad-txns-vout-negative")) {
|
||||||
throw BitcoinTransactionCommitFailedVoutNegative();
|
// throw BitcoinTransactionCommitFailedVoutNegative();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (error.contains("non-BIP68-final")) {
|
// if (error.contains("non-BIP68-final")) {
|
||||||
throw BitcoinTransactionCommitFailedBIP68Final();
|
// throw BitcoinTransactionCommitFailedBIP68Final();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (error.contains("min fee not met")) {
|
// if (error.contains("min fee not met")) {
|
||||||
throw BitcoinTransactionCommitFailedLessThanMin();
|
// throw BitcoinTransactionCommitFailedLessThanMin();
|
||||||
}
|
// }
|
||||||
|
|
||||||
throw BitcoinTransactionCommitFailed(errorMessage: error);
|
// throw BitcoinTransactionCommitFailed(errorMessage: error);
|
||||||
}
|
// }
|
||||||
|
|
||||||
throw BitcoinTransactionCommitFailed();
|
// throw BitcoinTransactionCommitFailed();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _ltcCommit() async {
|
Future<void> _ltcCommit() async {
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
|
||||||
|
|
||||||
ECPrivate generateECPrivate({
|
|
||||||
required Bip32Slip10Secp256k1 hd,
|
|
||||||
required BasedUtxoNetwork network,
|
|
||||||
required int index,
|
|
||||||
}) =>
|
|
||||||
ECPrivate(hd.childKey(Bip32KeyIndex(index)).privateKey);
|
|
||||||
|
|
||||||
String generateP2WPKHAddress({
|
|
||||||
required Bip32Slip10Secp256k1 hd,
|
|
||||||
required BasedUtxoNetwork network,
|
|
||||||
required int index,
|
|
||||||
}) =>
|
|
||||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
|
||||||
.toP2wpkhAddress()
|
|
||||||
.toAddress(network);
|
|
||||||
|
|
||||||
String generateP2SHAddress({
|
|
||||||
required Bip32Slip10Secp256k1 hd,
|
|
||||||
required BasedUtxoNetwork network,
|
|
||||||
required int index,
|
|
||||||
}) =>
|
|
||||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
|
||||||
.toP2wpkhInP2sh()
|
|
||||||
.toAddress(network);
|
|
||||||
|
|
||||||
String generateP2WSHAddress({
|
|
||||||
required Bip32Slip10Secp256k1 hd,
|
|
||||||
required BasedUtxoNetwork network,
|
|
||||||
required int index,
|
|
||||||
}) =>
|
|
||||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
|
||||||
.toP2wshAddress()
|
|
||||||
.toAddress(network);
|
|
||||||
|
|
||||||
String generateP2PKHAddress({
|
|
||||||
required Bip32Slip10Secp256k1 hd,
|
|
||||||
required BasedUtxoNetwork network,
|
|
||||||
required int index,
|
|
||||||
}) =>
|
|
||||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
|
||||||
.toP2pkhAddress()
|
|
||||||
.toAddress(network);
|
|
||||||
|
|
||||||
String generateP2TRAddress({
|
|
||||||
required Bip32Slip10Secp256k1 hd,
|
|
||||||
required BasedUtxoNetwork network,
|
|
||||||
required int index,
|
|
||||||
}) =>
|
|
||||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
|
||||||
.toTaprootAddress()
|
|
||||||
.toAddress(network);
|
|
|
@ -87,18 +87,18 @@ packages:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: cake-update-v8
|
ref: cake-update-v15
|
||||||
resolved-ref: fc045a11db3d85d806ca67f75e8b916c706745a2
|
resolved-ref: "49db5748d2edc73c0c8213e11ab6a39fa3a7ff7f"
|
||||||
url: "https://github.com/cake-tech/bitcoin_base"
|
url: "https://github.com/cake-tech/bitcoin_base.git"
|
||||||
source: git
|
source: git
|
||||||
version: "4.7.0"
|
version: "4.7.0"
|
||||||
blockchain_utils:
|
blockchain_utils:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: cake-update-v2
|
ref: cake-update-v3
|
||||||
resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
|
resolved-ref: "9b64c43bcfe129e7f01300a63607fde083dd0357"
|
||||||
url: "https://github.com/cake-tech/blockchain_utils"
|
url: "https://github.com/cake-tech/blockchain_utils.git"
|
||||||
source: git
|
source: git
|
||||||
version: "3.3.0"
|
version: "3.3.0"
|
||||||
bluez:
|
bluez:
|
||||||
|
@ -415,10 +415,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: google_identity_services_web
|
name: google_identity_services_web
|
||||||
sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6"
|
sha256: "0c56c2c5d60d6dfaf9725f5ad4699f04749fb196ee5a70487a46ef184837ccf6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1+4"
|
version: "0.3.0+2"
|
||||||
googleapis_auth:
|
googleapis_auth:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -471,10 +471,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.2"
|
version: "1.2.0"
|
||||||
http2:
|
http2:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -849,10 +849,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
sha256: "59dc807b94d29d52ddbb1b3c0d3b9d0a67fc535a64e62a5542c8db0513fcb6c2"
|
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.2.2"
|
||||||
shared_preferences_windows:
|
shared_preferences_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -918,9 +918,9 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "sp_v4.0.0"
|
ref: cake-update-v3
|
||||||
resolved-ref: ca1add293bd1e06920aa049b655832da50d0dab2
|
resolved-ref: "2c21e53fd652e0aee1ee5fcd891376c10334237b"
|
||||||
url: "https://github.com/cake-tech/sp_scanner"
|
url: "https://github.com/cake-tech/sp_scanner.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
|
@ -1047,18 +1047,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.4.2"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
|
sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.5"
|
version: "2.4.3"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -29,14 +29,14 @@ dependencies:
|
||||||
blockchain_utils:
|
blockchain_utils:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/blockchain_utils
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
ref: cake-update-v2
|
ref: cake-update-v3
|
||||||
cw_mweb:
|
cw_mweb:
|
||||||
path: ../cw_mweb
|
path: ../cw_mweb
|
||||||
grpc: ^3.2.4
|
grpc: ^3.2.4
|
||||||
sp_scanner:
|
sp_scanner:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/sp_scanner
|
url: https://github.com/cake-tech/sp_scanner.git
|
||||||
ref: sp_v4.0.0
|
ref: cake-update-v3
|
||||||
bech32:
|
bech32:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bech32.git
|
url: https://github.com/cake-tech/bech32.git
|
||||||
|
@ -63,8 +63,12 @@ dependency_overrides:
|
||||||
protobuf: ^3.1.0
|
protobuf: ^3.1.0
|
||||||
bitcoin_base:
|
bitcoin_base:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_base
|
url: https://github.com/cake-tech/bitcoin_base.git
|
||||||
ref: cake-update-v9
|
ref: cake-update-v15
|
||||||
|
blockchain_utils:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
|
ref: cake-update-v3
|
||||||
pointycastle: 3.7.4
|
pointycastle: 3.7.4
|
||||||
ffi: 2.1.0
|
ffi: 2.1.0
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
|
@ -37,46 +38,54 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
Map<String, int>? initialRegularAddressIndex,
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
Map<String, int>? initialChangeAddressIndex,
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
}) : super(
|
}) : super(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
network: BitcoinCashNetwork.mainnet,
|
network: BitcoinCashNetwork.mainnet,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
currency: CryptoCurrency.bch,
|
currency: CryptoCurrency.bch,
|
||||||
encryptionFileUtils: encryptionFileUtils,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
passphrase: passphrase) {
|
passphrase: passphrase,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
|
hdWallets: {CWBitcoinDerivationType.bip39: bitcoinCashHDWallet(seedBytes)},
|
||||||
|
) {
|
||||||
walletAddresses = BitcoinCashWalletAddresses(
|
walletAddresses = BitcoinCashWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
mainHd: hd,
|
|
||||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
|
||||||
network: network,
|
network: network,
|
||||||
initialAddressPageType: addressPageType,
|
initialAddressPageType: addressPageType,
|
||||||
isHardwareWallet: walletInfo.isHardwareWallet,
|
isHardwareWallet: walletInfo.isHardwareWallet,
|
||||||
|
hdWallets: hdWallets,
|
||||||
);
|
);
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<BitcoinCashWallet> create(
|
@override
|
||||||
{required String mnemonic,
|
BitcoinCashNetwork get network => BitcoinCashNetwork.mainnet;
|
||||||
required String password,
|
|
||||||
required WalletInfo walletInfo,
|
static Future<BitcoinCashWallet> create({
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required String mnemonic,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required String password,
|
||||||
String? passphrase,
|
required WalletInfo walletInfo,
|
||||||
String? addressPageType,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
ElectrumBalance? initialBalance,
|
String? passphrase,
|
||||||
Map<String, int>? initialRegularAddressIndex,
|
String? addressPageType,
|
||||||
Map<String, int>? initialChangeAddressIndex}) async {
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
|
ElectrumBalance? initialBalance,
|
||||||
|
Map<String, int>? initialRegularAddressIndex,
|
||||||
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
|
}) async {
|
||||||
return BitcoinCashWallet(
|
return BitcoinCashWallet(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
|
@ -90,6 +99,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
addressPageType: P2pkhAddressType.p2pkh,
|
addressPageType: P2pkhAddressType.p2pkh,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +109,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
|
|
||||||
|
@ -141,17 +152,21 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
return BitcoinAddressRecord(
|
return BitcoinAddressRecord(
|
||||||
addr.address,
|
addr.address,
|
||||||
index: addr.index,
|
index: addr.index,
|
||||||
isHidden: addr.isHidden,
|
isChange: addr.isChange,
|
||||||
type: P2pkhAddressType.p2pkh,
|
type: P2pkhAddressType.p2pkh,
|
||||||
network: BitcoinCashNetwork.mainnet,
|
network: BitcoinCashNetwork.mainnet,
|
||||||
|
derivationInfo: BitcoinAddressUtils.getDerivationFromType(P2pkhAddressType.p2pkh),
|
||||||
|
derivationType: CWBitcoinDerivationType.bip39,
|
||||||
);
|
);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return BitcoinAddressRecord(
|
return BitcoinAddressRecord(
|
||||||
AddressUtils.getCashAddrFormat(addr.address),
|
AddressUtils.getCashAddrFormat(addr.address),
|
||||||
index: addr.index,
|
index: addr.index,
|
||||||
isHidden: addr.isHidden,
|
isChange: addr.isChange,
|
||||||
type: P2pkhAddressType.p2pkh,
|
type: P2pkhAddressType.p2pkh,
|
||||||
network: BitcoinCashNetwork.mainnet,
|
network: BitcoinCashNetwork.mainnet,
|
||||||
|
derivationInfo: BitcoinAddressUtils.getDerivationFromType(P2pkhAddressType.p2pkh),
|
||||||
|
derivationType: CWBitcoinDerivationType.bip39,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}).toList(),
|
}).toList(),
|
||||||
|
@ -162,6 +177,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||||
addressPageType: P2pkhAddressType.p2pkh,
|
addressPageType: P2pkhAddressType.p2pkh,
|
||||||
passphrase: keysData.passphrase,
|
passphrase: keysData.passphrase,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,13 +209,13 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int feeRate(TransactionPriority priority) {
|
int feeRate(TransactionPriority priority) {
|
||||||
if (priority is BitcoinCashTransactionPriority) {
|
if (priority is ElectrumTransactionPriority) {
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case BitcoinCashTransactionPriority.slow:
|
case ElectrumTransactionPriority.slow:
|
||||||
return 1;
|
return 1;
|
||||||
case BitcoinCashTransactionPriority.medium:
|
case ElectrumTransactionPriority.medium:
|
||||||
return 5;
|
return 5;
|
||||||
case BitcoinCashTransactionPriority.fast:
|
case ElectrumTransactionPriority.fast:
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,17 +225,39 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> signMessage(String message, {String? address = null}) async {
|
Future<String> signMessage(String message, {String? address = null}) async {
|
||||||
int? index;
|
Bip32Slip10Secp256k1 HD = bip32;
|
||||||
try {
|
|
||||||
index = address != null
|
final record = walletAddresses.allAddresses.firstWhere((element) => element.address == address);
|
||||||
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
|
|
||||||
: null;
|
if (record.isChange) {
|
||||||
} catch (_) {}
|
HD = HD.childKey(Bip32KeyIndex(1));
|
||||||
final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
|
} else {
|
||||||
|
HD = HD.childKey(Bip32KeyIndex(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
HD = HD.childKey(Bip32KeyIndex(record.index));
|
||||||
final priv = ECPrivate.fromWif(
|
final priv = ECPrivate.fromWif(
|
||||||
WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
|
WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
|
||||||
netVersion: network.wifNetVer,
|
netVersion: network.wifNetVer,
|
||||||
);
|
);
|
||||||
return priv.signMessage(StringUtils.encode(message));
|
return priv.signMessage(StringUtils.encode(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> calcFee({
|
||||||
|
required List<UtxoWithAddress> utxos,
|
||||||
|
required List<BitcoinBaseOutput> outputs,
|
||||||
|
String? memo,
|
||||||
|
required int feeRate,
|
||||||
|
}) async =>
|
||||||
|
feeRate *
|
||||||
|
ForkedTransactionBuilder.estimateTransactionSize(
|
||||||
|
utxos: utxos,
|
||||||
|
outputs: outputs,
|
||||||
|
network: network,
|
||||||
|
memo: memo,
|
||||||
|
);
|
||||||
|
|
||||||
|
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(List<int> seedBytes) =>
|
||||||
|
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
@ -12,10 +10,9 @@ class BitcoinCashWalletAddresses = BitcoinCashWalletAddressesBase with _$Bitcoin
|
||||||
abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||||
BitcoinCashWalletAddressesBase(
|
BitcoinCashWalletAddressesBase(
|
||||||
WalletInfo walletInfo, {
|
WalletInfo walletInfo, {
|
||||||
required super.mainHd,
|
|
||||||
required super.sideHd,
|
|
||||||
required super.network,
|
required super.network,
|
||||||
required super.isHardwareWallet,
|
required super.isHardwareWallet,
|
||||||
|
required super.hdWallets,
|
||||||
super.initialAddresses,
|
super.initialAddresses,
|
||||||
super.initialRegularAddressIndex,
|
super.initialRegularAddressIndex,
|
||||||
super.initialChangeAddressIndex,
|
super.initialChangeAddressIndex,
|
||||||
|
@ -23,9 +20,17 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
|
||||||
}) : super(walletInfo);
|
}) : super(walletInfo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress(
|
BitcoinBaseAddress generateAddress({
|
||||||
{required int index,
|
required CWBitcoinDerivationType derivationType,
|
||||||
required Bip32Slip10Secp256k1 hd,
|
required bool isChange,
|
||||||
BitcoinAddressType? addressType}) =>
|
required int index,
|
||||||
generateP2PKHAddress(hd: hd, index: index, network: network);
|
required BitcoinAddressType addressType,
|
||||||
|
required BitcoinDerivationInfo derivationInfo,
|
||||||
|
}) =>
|
||||||
|
P2pkhAddress.fromDerivation(
|
||||||
|
bip32: bip32,
|
||||||
|
derivationInfo: derivationInfo,
|
||||||
|
isChange: isChange,
|
||||||
|
index: index,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,17 @@ class BitcoinCashWalletService extends WalletService<
|
||||||
BitcoinCashRestoreWalletFromSeedCredentials,
|
BitcoinCashRestoreWalletFromSeedCredentials,
|
||||||
BitcoinCashRestoreWalletFromWIFCredentials,
|
BitcoinCashRestoreWalletFromWIFCredentials,
|
||||||
BitcoinCashNewWalletCredentials> {
|
BitcoinCashNewWalletCredentials> {
|
||||||
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect);
|
BitcoinCashWalletService(
|
||||||
|
this.walletInfoSource,
|
||||||
|
this.unspentCoinsInfoSource,
|
||||||
|
this.isDirect,
|
||||||
|
this.mempoolAPIEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
final bool isDirect;
|
final bool isDirect;
|
||||||
|
final bool mempoolAPIEnabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletType getType() => WalletType.bitcoinCash;
|
WalletType getType() => WalletType.bitcoinCash;
|
||||||
|
@ -42,6 +48,7 @@ class BitcoinCashWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
passphrase: credentials.passphrase,
|
passphrase: credentials.passphrase,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -61,6 +68,7 @@ class BitcoinCashWalletService extends WalletService<
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
saveBackup(name);
|
saveBackup(name);
|
||||||
|
@ -73,6 +81,7 @@ class BitcoinCashWalletService extends WalletService<
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -92,11 +101,13 @@ class BitcoinCashWalletService extends WalletService<
|
||||||
final currentWalletInfo = walletInfoSource.values
|
final currentWalletInfo = walletInfoSource.values
|
||||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||||
final currentWallet = await BitcoinCashWalletBase.open(
|
final currentWallet = await BitcoinCashWalletBase.open(
|
||||||
password: password,
|
password: password,
|
||||||
name: currentName,
|
name: currentName,
|
||||||
walletInfo: currentWalletInfo,
|
walletInfo: currentWalletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
await currentWallet.renameWalletFiles(newName);
|
await currentWallet.renameWalletFiles(newName);
|
||||||
await saveBackup(newName);
|
await saveBackup(newName);
|
||||||
|
@ -128,12 +139,13 @@ class BitcoinCashWalletService extends WalletService<
|
||||||
}
|
}
|
||||||
|
|
||||||
final wallet = await BitcoinCashWalletBase.create(
|
final wallet = await BitcoinCashWalletBase.create(
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
mnemonic: credentials.mnemonic,
|
mnemonic: credentials.mnemonic,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
passphrase: credentials.passphrase
|
passphrase: credentials.passphrase,
|
||||||
|
mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:cw_bitcoin/exceptions.dart';
|
||||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||||
import 'package:cw_core/pending_transaction.dart';
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_bitcoin/electrum.dart';
|
import 'package:cw_bitcoin/electrum.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
@ -31,10 +31,10 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
||||||
String get hex => _tx.toHex();
|
String get hex => _tx.toHex();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
String get amountFormatted => BitcoinAmountUtils.bitcoinAmountToString(amount: amount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
String get feeFormatted => BitcoinAmountUtils.bitcoinAmountToString(amount: fee);
|
||||||
|
|
||||||
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
|
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
|
||||||
|
|
||||||
|
@ -74,15 +74,16 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
||||||
void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
|
void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
|
||||||
_listeners.add(listener);
|
_listeners.add(listener);
|
||||||
|
|
||||||
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(
|
||||||
id: id,
|
type,
|
||||||
height: 0,
|
id: id,
|
||||||
amount: amount,
|
height: 0,
|
||||||
direction: TransactionDirection.outgoing,
|
amount: amount,
|
||||||
date: DateTime.now(),
|
direction: TransactionDirection.outgoing,
|
||||||
isPending: true,
|
date: DateTime.now(),
|
||||||
confirmations: 0,
|
isPending: true,
|
||||||
fee: fee,
|
confirmations: 0,
|
||||||
isReplaced: false,
|
fee: fee,
|
||||||
|
isReplaced: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ dependencies:
|
||||||
blockchain_utils:
|
blockchain_utils:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/blockchain_utils
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
ref: cake-update-v2
|
ref: cake-update-v3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -41,8 +41,12 @@ dependency_overrides:
|
||||||
watcher: ^1.1.0
|
watcher: ^1.1.0
|
||||||
bitcoin_base:
|
bitcoin_base:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_base
|
url: https://github.com/cake-tech/bitcoin_base.git
|
||||||
ref: cake-update-v9
|
ref: cake-update-v15
|
||||||
|
blockchain_utils:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
|
ref: cake-update-v3
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
|
@ -45,7 +45,7 @@ class SyncedTipSyncStatus extends SyncedSyncStatus {
|
||||||
final int tip;
|
final int tip;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncronizingSyncStatus extends SyncStatus {
|
class SynchronizingSyncStatus extends SyncStatus {
|
||||||
@override
|
@override
|
||||||
double progress() => 0.0;
|
double progress() => 0.0;
|
||||||
}
|
}
|
||||||
|
@ -96,3 +96,58 @@ class LostConnectionSyncStatus extends NotConnectedSyncStatus {
|
||||||
@override
|
@override
|
||||||
String toString() => 'Reconnecting';
|
String toString() => 'Reconnecting';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> syncStatusToJson(SyncStatus? status) {
|
||||||
|
if (status == null) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'progress': status.progress(),
|
||||||
|
'type': status.runtimeType.toString(),
|
||||||
|
'data': status is SyncingSyncStatus
|
||||||
|
? {'blocksLeft': status.blocksLeft, 'ptc': status.ptc}
|
||||||
|
: status is SyncedTipSyncStatus
|
||||||
|
? {'tip': status.tip}
|
||||||
|
: status is FailedSyncStatus
|
||||||
|
? {'error': status.error}
|
||||||
|
: status is StartingScanSyncStatus
|
||||||
|
? {'beginHeight': status.beginHeight}
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncStatus syncStatusFromJson(Map<String, dynamic> json) {
|
||||||
|
final type = json['type'] as String;
|
||||||
|
final data = json['data'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'StartingScanSyncStatus':
|
||||||
|
return StartingScanSyncStatus(data!['beginHeight'] as int);
|
||||||
|
case 'SyncingSyncStatus':
|
||||||
|
return SyncingSyncStatus(data!['blocksLeft'] as int, data['ptc'] as double);
|
||||||
|
case 'SyncedTipSyncStatus':
|
||||||
|
return SyncedTipSyncStatus(data!['tip'] as int);
|
||||||
|
case 'FailedSyncStatus':
|
||||||
|
return FailedSyncStatus(error: data!['error'] as String?);
|
||||||
|
case 'SynchronizingSyncStatus':
|
||||||
|
return SynchronizingSyncStatus();
|
||||||
|
case 'NotConnectedSyncStatus':
|
||||||
|
return NotConnectedSyncStatus();
|
||||||
|
case 'AttemptingSyncStatus':
|
||||||
|
return AttemptingSyncStatus();
|
||||||
|
case 'AttemptingScanSyncStatus':
|
||||||
|
return AttemptingScanSyncStatus();
|
||||||
|
case 'ConnectedSyncStatus':
|
||||||
|
return ConnectedSyncStatus();
|
||||||
|
case 'ConnectingSyncStatus':
|
||||||
|
return ConnectingSyncStatus();
|
||||||
|
case 'UnsupportedSyncStatus':
|
||||||
|
return UnsupportedSyncStatus();
|
||||||
|
case 'TimedOutSyncStatus':
|
||||||
|
return TimedOutSyncStatus();
|
||||||
|
case 'LostConnectionSyncStatus':
|
||||||
|
return LostConnectionSyncStatus();
|
||||||
|
default:
|
||||||
|
throw Exception('Unknown sync status type: $type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
import 'package:cw_core/enumerable_item.dart';
|
import 'package:cw_core/enumerable_item.dart';
|
||||||
|
|
||||||
abstract class TransactionPriority extends EnumerableItem<int>
|
abstract class TransactionPriority extends EnumerableItem<int> with Serializable<int> {
|
||||||
with Serializable<int> {
|
const TransactionPriority({required super.title, required super.raw});
|
||||||
const TransactionPriority({required String title, required int raw}) : super(title: title, raw: raw);
|
|
||||||
|
String get units => '';
|
||||||
|
String toString() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class TransactionPriorities {
|
||||||
|
const TransactionPriorities();
|
||||||
|
int operator [](TransactionPriority type);
|
||||||
|
String labelWithRate(TransactionPriority type);
|
||||||
|
Map<String, int> toJson();
|
||||||
|
factory TransactionPriorities.fromJson(Map<String, int> json) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ abstract class WalletCredentials {
|
||||||
this.password,
|
this.password,
|
||||||
this.passphrase,
|
this.passphrase,
|
||||||
this.derivationInfo,
|
this.derivationInfo,
|
||||||
|
this.derivations,
|
||||||
this.hardwareWalletType,
|
this.hardwareWalletType,
|
||||||
this.parentAddress,
|
this.parentAddress,
|
||||||
}) {
|
}) {
|
||||||
|
@ -25,5 +26,6 @@ abstract class WalletCredentials {
|
||||||
String? passphrase;
|
String? passphrase;
|
||||||
WalletInfo? walletInfo;
|
WalletInfo? walletInfo;
|
||||||
DerivationInfo? derivationInfo;
|
DerivationInfo? derivationInfo;
|
||||||
|
List<DerivationInfo>? derivations;
|
||||||
HardwareWalletType? hardwareWalletType;
|
HardwareWalletType? hardwareWalletType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ class WalletInfo extends HiveObject {
|
||||||
this.yatLastUsedAddressRaw,
|
this.yatLastUsedAddressRaw,
|
||||||
this.showIntroCakePayCard,
|
this.showIntroCakePayCard,
|
||||||
this.derivationInfo,
|
this.derivationInfo,
|
||||||
|
this.derivations,
|
||||||
this.hardwareWalletType,
|
this.hardwareWalletType,
|
||||||
this.parentAddress,
|
this.parentAddress,
|
||||||
) : _yatLastUsedAddressController = StreamController<String>.broadcast();
|
) : _yatLastUsedAddressController = StreamController<String>.broadcast();
|
||||||
|
@ -97,6 +98,7 @@ class WalletInfo extends HiveObject {
|
||||||
String yatEid = '',
|
String yatEid = '',
|
||||||
String yatLastUsedAddressRaw = '',
|
String yatLastUsedAddressRaw = '',
|
||||||
DerivationInfo? derivationInfo,
|
DerivationInfo? derivationInfo,
|
||||||
|
List<DerivationInfo>? derivations,
|
||||||
HardwareWalletType? hardwareWalletType,
|
HardwareWalletType? hardwareWalletType,
|
||||||
String? parentAddress,
|
String? parentAddress,
|
||||||
}) {
|
}) {
|
||||||
|
@ -114,6 +116,7 @@ class WalletInfo extends HiveObject {
|
||||||
yatLastUsedAddressRaw,
|
yatLastUsedAddressRaw,
|
||||||
showIntroCakePayCard,
|
showIntroCakePayCard,
|
||||||
derivationInfo,
|
derivationInfo,
|
||||||
|
derivations,
|
||||||
hardwareWalletType,
|
hardwareWalletType,
|
||||||
parentAddress,
|
parentAddress,
|
||||||
);
|
);
|
||||||
|
@ -189,15 +192,15 @@ class WalletInfo extends HiveObject {
|
||||||
|
|
||||||
@HiveField(22)
|
@HiveField(22)
|
||||||
String? parentAddress;
|
String? parentAddress;
|
||||||
|
|
||||||
@HiveField(23)
|
@HiveField(23)
|
||||||
List<String>? hiddenAddresses;
|
List<String>? hiddenAddresses;
|
||||||
|
|
||||||
@HiveField(24)
|
@HiveField(24)
|
||||||
List<String>? manualAddresses;
|
List<String>? manualAddresses;
|
||||||
|
|
||||||
|
@HiveField(25)
|
||||||
|
List<DerivationInfo>? derivations;
|
||||||
|
|
||||||
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,10 @@ mixin WalletKeysFile<BalanceType extends Balance, HistoryType extends Transactio
|
||||||
final path = "$rootPath${isBackup ? ".backup" : ""}";
|
final path = "$rootPath${isBackup ? ".backup" : ""}";
|
||||||
dev.log("Saving .keys file '$path'");
|
dev.log("Saving .keys file '$path'");
|
||||||
await encryptionFileUtils.write(
|
await encryptionFileUtils.write(
|
||||||
path: path, password: password, data: walletKeysData.toJSON());
|
path: path,
|
||||||
|
password: password,
|
||||||
|
data: walletKeysData.toJSON(),
|
||||||
|
);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount<Accou
|
||||||
@observable
|
@observable
|
||||||
String address;
|
String address;
|
||||||
|
|
||||||
|
String get primaryAddress => address;
|
||||||
|
|
||||||
// @override
|
// @override
|
||||||
@observable
|
@observable
|
||||||
Account? account;
|
Account? account;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
/home/rafael/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/
|
|
@ -17,12 +17,12 @@ dependencies:
|
||||||
path: ../cw_evm
|
path: ../cw_evm
|
||||||
on_chain:
|
on_chain:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/On_chain
|
url: https://github.com/cake-tech/on_chain.git
|
||||||
ref: cake-update-v2
|
ref: cake-update-v3
|
||||||
blockchain_utils:
|
blockchain_utils:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/blockchain_utils
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
ref: cake-update-v2
|
ref: cake-update-v3
|
||||||
mobx: ^2.3.0+1
|
mobx: ^2.3.0+1
|
||||||
bip39: ^1.0.6
|
bip39: ^1.0.6
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
|
@ -34,6 +34,13 @@ dev_dependencies:
|
||||||
build_runner: ^2.3.3
|
build_runner: ^2.3.3
|
||||||
mobx_codegen: ^2.1.1
|
mobx_codegen: ^2.1.1
|
||||||
hive_generator: ^1.1.3
|
hive_generator: ^1.1.3
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
blockchain_utils:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
|
ref: cake-update-v3
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
# assets:
|
# assets:
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
|
|
|
@ -5,17 +5,15 @@ class CWBitcoin extends Bitcoin {
|
||||||
required String name,
|
required String name,
|
||||||
required String mnemonic,
|
required String mnemonic,
|
||||||
required String password,
|
required String password,
|
||||||
required DerivationType derivationType,
|
required List<DerivationInfo>? derivations,
|
||||||
required String derivationPath,
|
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
}) =>
|
}) =>
|
||||||
BitcoinRestoreWalletFromSeedCredentials(
|
BitcoinRestoreWalletFromSeedCredentials(
|
||||||
name: name,
|
name: name,
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
derivationType: derivationType,
|
|
||||||
derivationPath: derivationPath,
|
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
|
derivations: derivations,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -54,7 +52,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
name: name, hwAccountData: accountData, walletInfo: walletInfo);
|
name: name, hwAccountData: accountData, walletInfo: walletInfo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getMediumTransactionPriority() => BitcoinTransactionPriority.medium;
|
TransactionPriority getMediumTransactionPriority() => ElectrumTransactionPriority.medium;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> getWordList() => wordlist;
|
List<String> getWordList() => wordlist;
|
||||||
|
@ -72,18 +70,18 @@ class CWBitcoin extends Bitcoin {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<TransactionPriority> getTransactionPriorities() => BitcoinTransactionPriority.all;
|
List<TransactionPriority> getTransactionPriorities() => ElectrumTransactionPriority.all;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<TransactionPriority> getLitecoinTransactionPriorities() => LitecoinTransactionPriority.all;
|
List<TransactionPriority> getLitecoinTransactionPriorities() => ElectrumTransactionPriority.all;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority deserializeBitcoinTransactionPriority(int raw) =>
|
TransactionPriority deserializeBitcoinTransactionPriority(int raw) =>
|
||||||
BitcoinTransactionPriority.deserialize(raw: raw);
|
ElectrumTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority deserializeLitecoinTransactionPriority(int raw) =>
|
TransactionPriority deserializeLitecoinTransactionPriority(int raw) =>
|
||||||
LitecoinTransactionPriority.deserialize(raw: raw);
|
ElectrumTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int getFeeRate(Object wallet, TransactionPriority priority) {
|
int getFeeRate(Object wallet, TransactionPriority priority) {
|
||||||
|
@ -113,7 +111,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||||
}) {
|
}) {
|
||||||
final bitcoinFeeRate =
|
final bitcoinFeeRate =
|
||||||
priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null;
|
priority == ElectrumTransactionPriority.custom && feeRate != null ? feeRate : null;
|
||||||
return BitcoinTransactionCredentials(
|
return BitcoinTransactionCredentials(
|
||||||
outputs
|
outputs
|
||||||
.map((out) => OutputInfo(
|
.map((out) => OutputInfo(
|
||||||
|
@ -127,7 +125,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||||
memo: out.memo))
|
memo: out.memo))
|
||||||
.toList(),
|
.toList(),
|
||||||
priority: priority as BitcoinTransactionPriority,
|
priority: priority as ElectrumTransactionPriority,
|
||||||
feeRate: bitcoinFeeRate,
|
feeRate: bitcoinFeeRate,
|
||||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||||
);
|
);
|
||||||
|
@ -144,7 +142,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
address: addr.address,
|
address: addr.address,
|
||||||
txCount: addr.txCount,
|
txCount: addr.txCount,
|
||||||
balance: addr.balance,
|
balance: addr.balance,
|
||||||
isChange: addr.isHidden))
|
isChange: addr.isChange))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,12 +165,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
final p2shAddr = sk.getPublic().toP2pkhInP2sh();
|
final p2shAddr = sk.getPublic().toP2pkhInP2sh();
|
||||||
final estimatedTx = await electrumWallet.estimateSendAllTx(
|
final estimatedTx = await electrumWallet.estimateSendAllTx(
|
||||||
[BitcoinOutput(address: p2shAddr, value: BigInt.zero)],
|
[BitcoinOutput(address: p2shAddr, value: BigInt.zero)],
|
||||||
getFeeRate(
|
getFeeRate(wallet, priority),
|
||||||
wallet,
|
|
||||||
wallet.type == WalletType.litecoin
|
|
||||||
? priority as LitecoinTransactionPriority
|
|
||||||
: priority as BitcoinTransactionPriority,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return estimatedTx.amount;
|
return estimatedTx.amount;
|
||||||
|
@ -189,19 +182,20 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String formatterBitcoinAmountToString({required int amount}) =>
|
String formatterBitcoinAmountToString({required int amount}) =>
|
||||||
bitcoinAmountToString(amount: amount);
|
BitcoinAmountUtils.bitcoinAmountToString(amount: amount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double formatterBitcoinAmountToDouble({required int amount}) =>
|
double formatterBitcoinAmountToDouble({required int amount}) =>
|
||||||
bitcoinAmountToDouble(amount: amount);
|
BitcoinAmountUtils.bitcoinAmountToDouble(amount: amount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
|
int formatterStringDoubleToBitcoinAmount(String amount) =>
|
||||||
|
BitcoinAmountUtils.stringDoubleToBitcoinAmount(amount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate,
|
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate,
|
||||||
{int? customRate}) =>
|
{int? customRate}) =>
|
||||||
(priority as BitcoinTransactionPriority).labelWithRate(rate, customRate);
|
(priority as ElectrumTransactionPriority).labelWithRate(rate, customRate);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<BitcoinUnspent> getUnspents(Object wallet,
|
List<BitcoinUnspent> getUnspents(Object wallet,
|
||||||
|
@ -224,30 +218,52 @@ class CWBitcoin extends Bitcoin {
|
||||||
await bitcoinWallet.updateAllUnspents();
|
await bitcoinWallet.updateAllUnspents();
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource,
|
WalletService createBitcoinWalletService(
|
||||||
Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect) {
|
Box<WalletInfo> walletInfoSource,
|
||||||
return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan, isDirect);
|
Box<UnspentCoinsInfo> unspentCoinSource,
|
||||||
|
bool alwaysScan,
|
||||||
|
bool isDirect,
|
||||||
|
bool mempoolAPIEnabled,
|
||||||
|
) {
|
||||||
|
return BitcoinWalletService(
|
||||||
|
walletInfoSource,
|
||||||
|
unspentCoinSource,
|
||||||
|
alwaysScan,
|
||||||
|
isDirect,
|
||||||
|
mempoolAPIEnabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource,
|
WalletService createLitecoinWalletService(
|
||||||
Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect) {
|
Box<WalletInfo> walletInfoSource,
|
||||||
return LitecoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan, isDirect);
|
Box<UnspentCoinsInfo> unspentCoinSource,
|
||||||
|
bool alwaysScan,
|
||||||
|
bool isDirect,
|
||||||
|
bool mempoolAPIEnabled,
|
||||||
|
) {
|
||||||
|
return LitecoinWalletService(
|
||||||
|
walletInfoSource,
|
||||||
|
unspentCoinSource,
|
||||||
|
alwaysScan,
|
||||||
|
isDirect,
|
||||||
|
mempoolAPIEnabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getBitcoinTransactionPriorityMedium() => BitcoinTransactionPriority.medium;
|
TransactionPriority getBitcoinTransactionPriorityMedium() => ElectrumTransactionPriority.fast;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getBitcoinTransactionPriorityCustom() => BitcoinTransactionPriority.custom;
|
TransactionPriority getBitcoinTransactionPriorityCustom() => ElectrumTransactionPriority.custom;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getLitecoinTransactionPriorityMedium() => LitecoinTransactionPriority.medium;
|
TransactionPriority getLitecoinTransactionPriorityMedium() => ElectrumTransactionPriority.medium;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getBitcoinTransactionPrioritySlow() => BitcoinTransactionPriority.slow;
|
TransactionPriority getBitcoinTransactionPrioritySlow() => ElectrumTransactionPriority.medium;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getLitecoinTransactionPrioritySlow() => LitecoinTransactionPriority.slow;
|
TransactionPriority getLitecoinTransactionPrioritySlow() => ElectrumTransactionPriority.slow;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> setAddressType(Object wallet, dynamic option) async {
|
Future<void> setAddressType(Object wallet, dynamic option) async {
|
||||||
|
@ -320,20 +336,12 @@ class CWBitcoin extends Bitcoin {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<DerivationInfo>> getDerivationsFromMnemonic({
|
Future<List<BitcoinDerivationInfo>> getDerivationsFromMnemonic({
|
||||||
required String mnemonic,
|
required String mnemonic,
|
||||||
required Node node,
|
required Node node,
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
}) async {
|
}) async {
|
||||||
List<DerivationInfo> list = [];
|
List<BitcoinDerivationInfo> list = [];
|
||||||
|
|
||||||
List<DerivationType> types = await compareDerivationMethods(mnemonic: mnemonic, node: node);
|
|
||||||
if (types.length == 1 && types.first == DerivationType.electrum) {
|
|
||||||
return [getElectrumDerivations()[DerivationType.electrum]!.first];
|
|
||||||
}
|
|
||||||
|
|
||||||
final electrumClient = ElectrumClient();
|
|
||||||
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
|
||||||
|
|
||||||
late BasedUtxoNetwork network;
|
late BasedUtxoNetwork network;
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
|
@ -346,72 +354,34 @@ class CWBitcoin extends Bitcoin {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DerivationType dType in electrum_derivations.keys) {
|
var electrumSeedBytes;
|
||||||
late Uint8List seedBytes;
|
try {
|
||||||
if (dType == DerivationType.electrum) {
|
electrumSeedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
|
||||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
} catch (e) {
|
||||||
} else if (dType == DerivationType.bip39) {
|
print("electrum_v2 seed error: $e");
|
||||||
seedBytes = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (DerivationInfo dInfo in electrum_derivations[dType]!) {
|
if (passphrase != null && passphrase.isEmpty) {
|
||||||
try {
|
try {
|
||||||
DerivationInfo dInfoCopy = DerivationInfo(
|
// TODO: language pick
|
||||||
derivationType: dInfo.derivationType,
|
electrumSeedBytes = ElectrumV1SeedGenerator(mnemonic).generate();
|
||||||
derivationPath: dInfo.derivationPath,
|
} catch (e) {
|
||||||
description: dInfo.description,
|
print("electrum_v1 seed error: $e");
|
||||||
scriptType: dInfo.scriptType,
|
|
||||||
);
|
|
||||||
|
|
||||||
String balancePath = dInfoCopy.derivationPath!;
|
|
||||||
int derivationDepth = _countCharOccurrences(balancePath, '/');
|
|
||||||
|
|
||||||
// for BIP44
|
|
||||||
if (derivationDepth == 3 || derivationDepth == 1) {
|
|
||||||
// we add "/0" so that we generate account 0
|
|
||||||
balancePath += "/0";
|
|
||||||
}
|
|
||||||
|
|
||||||
final hd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(balancePath)
|
|
||||||
as Bip32Slip10Secp256k1;
|
|
||||||
|
|
||||||
// derive address at index 0:
|
|
||||||
String? address;
|
|
||||||
switch (dInfoCopy.scriptType) {
|
|
||||||
case "p2wpkh":
|
|
||||||
address = generateP2WPKHAddress(hd: hd, network: network, index: 0);
|
|
||||||
break;
|
|
||||||
case "p2pkh":
|
|
||||||
address = generateP2PKHAddress(hd: hd, network: network, index: 0);
|
|
||||||
break;
|
|
||||||
case "p2wpkh-p2sh":
|
|
||||||
address = generateP2SHAddress(hd: hd, network: network, index: 0);
|
|
||||||
break;
|
|
||||||
case "p2tr":
|
|
||||||
address = generateP2TRAddress(hd: hd, network: network, index: 0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final sh = BitcoinAddressUtils.scriptHash(address, network: network);
|
|
||||||
final history = await electrumClient.getHistory(sh);
|
|
||||||
|
|
||||||
final balance = await electrumClient.getBalance(sh);
|
|
||||||
dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
|
|
||||||
dInfoCopy.address = address;
|
|
||||||
dInfoCopy.transactionsCount = history.length;
|
|
||||||
|
|
||||||
list.add(dInfoCopy);
|
|
||||||
} catch (e, s) {
|
|
||||||
print("derivationInfoError: $e");
|
|
||||||
print("derivationInfoStack: $s");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort the list such that derivations with the most transactions are first:
|
if (electrumSeedBytes != null) {
|
||||||
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
|
list.add(BitcoinDerivationInfos.ELECTRUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bip39SeedBytes;
|
||||||
|
try {
|
||||||
|
bip39SeedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (bip39SeedBytes != null) {
|
||||||
|
list.add(BitcoinDerivationInfos.BIP84);
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -443,7 +413,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
@override
|
@override
|
||||||
int getTransactionVSize(Object wallet, String transactionHex) {
|
int getTransactionVSize(Object wallet, String transactionHex) {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
return bitcoinWallet.transactionVSize(transactionHex);
|
return BtcTransaction.fromRaw(transactionHex).getVSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -458,7 +428,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
{int? size}) {
|
{int? size}) {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
return bitcoinWallet.feeAmountForPriority(
|
return bitcoinWallet.feeAmountForPriority(
|
||||||
priority as BitcoinTransactionPriority, inputsCount, outputsCount);
|
priority as ElectrumTransactionPriority, inputsCount, outputsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -482,8 +452,13 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int getMaxCustomFeeRate(Object wallet) {
|
int getMaxCustomFeeRate(Object wallet) {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final electrumWallet = wallet as ElectrumWallet;
|
||||||
return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 10).round();
|
final feeRates = electrumWallet.feeRates;
|
||||||
|
final maxFee = electrumWallet.feeRates is ElectrumTransactionPriorities
|
||||||
|
? ElectrumTransactionPriority.fast
|
||||||
|
: BitcoinTransactionPriority.priority;
|
||||||
|
|
||||||
|
return (electrumWallet.feeRate(maxFee) * 10).round();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -526,7 +501,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
address: addr.address,
|
address: addr.address,
|
||||||
txCount: addr.txCount,
|
txCount: addr.txCount,
|
||||||
balance: addr.balance,
|
balance: addr.balance,
|
||||||
isChange: addr.isHidden))
|
isChange: addr.isChange))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,7 +516,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
address: addr.address,
|
address: addr.address,
|
||||||
txCount: addr.txCount,
|
txCount: addr.txCount,
|
||||||
balance: addr.balance,
|
balance: addr.balance,
|
||||||
isChange: addr.isHidden))
|
isChange: addr.isChange))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,7 +539,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> setScanningActive(Object wallet, bool active) async {
|
Future<void> setScanningActive(Object wallet, bool active) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as BitcoinWallet;
|
||||||
bitcoinWallet.setSilentPaymentsScanning(active);
|
bitcoinWallet.setSilentPaymentsScanning(active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -574,10 +549,16 @@ class CWBitcoin extends Bitcoin {
|
||||||
return bitcoinWallet.isTestnet;
|
return bitcoinWallet.isTestnet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> registerSilentPaymentsKey(Object wallet, bool active) async {
|
||||||
|
final bitcoinWallet = wallet as BitcoinWallet;
|
||||||
|
return await bitcoinWallet.registerSilentPaymentsKey();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> checkIfMempoolAPIIsEnabled(Object wallet) async {
|
Future<bool> checkIfMempoolAPIIsEnabled(Object wallet) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
return await bitcoinWallet.checkIfMempoolAPIIsEnabled();
|
return await bitcoinWallet.mempoolAPIEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -595,13 +576,13 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as BitcoinWallet;
|
||||||
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
|
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
|
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as BitcoinWallet;
|
||||||
return bitcoinWallet.getNodeSupportsSilentPayments();
|
return bitcoinWallet.getNodeSupportsSilentPayments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,17 @@ class CWBitcoinCash extends BitcoinCash {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletService createBitcoinCashWalletService(
|
WalletService createBitcoinCashWalletService(
|
||||||
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool isDirect) {
|
Box<WalletInfo> walletInfoSource,
|
||||||
return BitcoinCashWalletService(walletInfoSource, unspentCoinSource, isDirect);
|
Box<UnspentCoinsInfo> unspentCoinSource,
|
||||||
|
bool isDirect,
|
||||||
|
bool mempoolAPIEnabled,
|
||||||
|
) {
|
||||||
|
return BitcoinCashWalletService(
|
||||||
|
walletInfoSource,
|
||||||
|
unspentCoinSource,
|
||||||
|
isDirect,
|
||||||
|
mempoolAPIEnabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -30,21 +39,23 @@ class CWBitcoinCash extends BitcoinCash {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials(
|
WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials(
|
||||||
{required String name, required String mnemonic, required String password, String? passphrase}) =>
|
{required String name,
|
||||||
|
required String mnemonic,
|
||||||
|
required String password,
|
||||||
|
String? passphrase}) =>
|
||||||
BitcoinCashRestoreWalletFromSeedCredentials(
|
BitcoinCashRestoreWalletFromSeedCredentials(
|
||||||
name: name, mnemonic: mnemonic, password: password, passphrase: passphrase);
|
name: name, mnemonic: mnemonic, password: password, passphrase: passphrase);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) =>
|
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) =>
|
||||||
BitcoinCashTransactionPriority.deserialize(raw: raw);
|
ElectrumTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getDefaultTransactionPriority() => BitcoinCashTransactionPriority.medium;
|
TransactionPriority getDefaultTransactionPriority() => ElectrumTransactionPriority.medium;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<TransactionPriority> getTransactionPriorities() => BitcoinCashTransactionPriority.all;
|
List<TransactionPriority> getTransactionPriorities() => ElectrumTransactionPriority.all;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TransactionPriority getBitcoinCashTransactionPrioritySlow() =>
|
TransactionPriority getBitcoinCashTransactionPrioritySlow() => ElectrumTransactionPriority.slow;
|
||||||
BitcoinCashTransactionPriority.slow;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
||||||
return S.current.sync_status_timed_out;
|
return S.current.sync_status_timed_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncStatus is SyncronizingSyncStatus) {
|
if (syncStatus is SynchronizingSyncStatus) {
|
||||||
return S.current.sync_status_syncronizing;
|
return S.current.sync_status_syncronizing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -267,6 +267,8 @@ abstract class Web3WalletServiceBase with Store {
|
||||||
|
|
||||||
final keyForWallet = getKeyForStoringTopicsForWallet();
|
final keyForWallet = getKeyForStoringTopicsForWallet();
|
||||||
|
|
||||||
|
if (keyForWallet.isEmpty) return;
|
||||||
|
|
||||||
final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet);
|
final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet);
|
||||||
|
|
||||||
final filteredPairings =
|
final filteredPairings =
|
||||||
|
@ -360,6 +362,10 @@ abstract class Web3WalletServiceBase with Store {
|
||||||
String getKeyForStoringTopicsForWallet() {
|
String getKeyForStoringTopicsForWallet() {
|
||||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
|
||||||
|
|
||||||
|
if (chainKeys.isEmpty) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
final keyForPairingTopic =
|
final keyForPairingTopic =
|
||||||
PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
|
PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
|
||||||
|
|
||||||
|
@ -386,6 +392,8 @@ abstract class Web3WalletServiceBase with Store {
|
||||||
// Get key specific to the current wallet
|
// Get key specific to the current wallet
|
||||||
final key = getKeyForStoringTopicsForWallet();
|
final key = getKeyForStoringTopicsForWallet();
|
||||||
|
|
||||||
|
if (key.isEmpty) return;
|
||||||
|
|
||||||
// Get all pairing topics attached to this key
|
// Get all pairing topics attached to this key
|
||||||
final pairingTopicsForWallet = getPairingTopicsForWallet(key);
|
final pairingTopicsForWallet = getPairingTopicsForWallet(key);
|
||||||
|
|
||||||
|
|
170
lib/di.dart
170
lib/di.dart
|
@ -367,14 +367,14 @@ Future<void> setup({
|
||||||
(WalletType type) => getIt.get<WalletService>(param1: type)));
|
(WalletType type) => getIt.get<WalletService>(param1: type)));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletNewVM, NewWalletArguments, void>(
|
getIt.registerFactoryParam<WalletNewVM, NewWalletArguments, void>(
|
||||||
(newWalletArgs, _) => WalletNewVM(
|
(newWalletArgs, _) => WalletNewVM(
|
||||||
getIt.get<AppStore>(),
|
getIt.get<AppStore>(),
|
||||||
getIt.get<WalletCreationService>(param1:newWalletArgs.type),
|
getIt.get<WalletCreationService>(param1: newWalletArgs.type),
|
||||||
_walletInfoSource,
|
_walletInfoSource,
|
||||||
getIt.get<AdvancedPrivacySettingsViewModel>(param1: newWalletArgs.type),
|
getIt.get<AdvancedPrivacySettingsViewModel>(param1: newWalletArgs.type),
|
||||||
getIt.get<SeedSettingsViewModel>(),
|
getIt.get<SeedSettingsViewModel>(),
|
||||||
newWalletArguments: newWalletArgs,));
|
newWalletArguments: newWalletArgs,
|
||||||
|
));
|
||||||
|
|
||||||
getIt.registerFactory<NewWalletTypeViewModel>(() => NewWalletTypeViewModel(_walletInfoSource));
|
getIt.registerFactory<NewWalletTypeViewModel>(() => NewWalletTypeViewModel(_walletInfoSource));
|
||||||
|
|
||||||
|
@ -397,62 +397,52 @@ Future<void> setup({
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
||||||
return WalletUnlockPage(
|
return WalletUnlockPage(getIt.get<WalletUnlockLoadableViewModel>(param1: args), args.callback,
|
||||||
getIt.get<WalletUnlockLoadableViewModel>(param1: args),
|
args.authPasswordHandler,
|
||||||
args.callback,
|
closable: closable);
|
||||||
args.authPasswordHandler,
|
|
||||||
closable: closable);
|
|
||||||
}, instanceName: 'wallet_unlock_loadable');
|
}, instanceName: 'wallet_unlock_loadable');
|
||||||
|
|
||||||
getIt.registerFactory<WalletUnlockPage>(
|
getIt.registerFactory<WalletUnlockPage>(
|
||||||
() => getIt.get<WalletUnlockPage>(
|
() => getIt.get<WalletUnlockPage>(
|
||||||
param1: WalletUnlockArguments(
|
param1: WalletUnlockArguments(callback: (bool successful, _) {
|
||||||
callback: (bool successful, _) {
|
if (successful) {
|
||||||
if (successful) {
|
final authStore = getIt.get<AuthenticationStore>();
|
||||||
final authStore = getIt.get<AuthenticationStore>();
|
authStore.allowed();
|
||||||
authStore.allowed();
|
}
|
||||||
}}),
|
}),
|
||||||
param2: false,
|
param2: false,
|
||||||
instanceName: 'wallet_unlock_loadable'),
|
instanceName: 'wallet_unlock_loadable'),
|
||||||
instanceName: 'wallet_password_login');
|
instanceName: 'wallet_password_login');
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
||||||
return WalletUnlockPage(
|
return WalletUnlockPage(getIt.get<WalletUnlockVerifiableViewModel>(param1: args), args.callback,
|
||||||
getIt.get<WalletUnlockVerifiableViewModel>(param1: args),
|
args.authPasswordHandler,
|
||||||
args.callback,
|
closable: closable);
|
||||||
args.authPasswordHandler,
|
|
||||||
closable: closable);
|
|
||||||
}, instanceName: 'wallet_unlock_verifiable');
|
}, instanceName: 'wallet_unlock_verifiable');
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>((args, _) {
|
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>((args, _) {
|
||||||
final currentWalletName = getIt
|
final currentWalletName =
|
||||||
.get<SharedPreferences>()
|
getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName) ?? '';
|
||||||
.getString(PreferencesKey.currentWalletName) ?? '';
|
|
||||||
final currentWalletTypeRaw =
|
final currentWalletTypeRaw =
|
||||||
getIt.get<SharedPreferences>()
|
getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType) ?? 0;
|
||||||
.getInt(PreferencesKey.currentWalletType) ?? 0;
|
|
||||||
final currentWalletType = deserializeFromInt(currentWalletTypeRaw);
|
final currentWalletType = deserializeFromInt(currentWalletTypeRaw);
|
||||||
|
|
||||||
return WalletUnlockLoadableViewModel(
|
return WalletUnlockLoadableViewModel(getIt.get<AppStore>(), getIt.get<WalletLoadingService>(),
|
||||||
getIt.get<AppStore>(),
|
walletName: args.walletName ?? currentWalletName,
|
||||||
getIt.get<WalletLoadingService>(),
|
walletType: args.walletType ?? currentWalletType);
|
||||||
walletName: args.walletName ?? currentWalletName,
|
|
||||||
walletType: args.walletType ?? currentWalletType);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletUnlockVerifiableViewModel, WalletUnlockArguments, void>((args, _) {
|
getIt.registerFactoryParam<WalletUnlockVerifiableViewModel, WalletUnlockArguments, void>(
|
||||||
final currentWalletName = getIt
|
(args, _) {
|
||||||
.get<SharedPreferences>()
|
final currentWalletName =
|
||||||
.getString(PreferencesKey.currentWalletName) ?? '';
|
getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName) ?? '';
|
||||||
final currentWalletTypeRaw =
|
final currentWalletTypeRaw =
|
||||||
getIt.get<SharedPreferences>()
|
getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType) ?? 0;
|
||||||
.getInt(PreferencesKey.currentWalletType) ?? 0;
|
|
||||||
final currentWalletType = deserializeFromInt(currentWalletTypeRaw);
|
final currentWalletType = deserializeFromInt(currentWalletTypeRaw);
|
||||||
|
|
||||||
return WalletUnlockVerifiableViewModel(
|
return WalletUnlockVerifiableViewModel(getIt.get<AppStore>(),
|
||||||
getIt.get<AppStore>(),
|
walletName: args.walletName ?? currentWalletName,
|
||||||
walletName: args.walletName ?? currentWalletName,
|
walletType: args.walletType ?? currentWalletType);
|
||||||
walletType: args.walletType ?? currentWalletType);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) =>
|
getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) =>
|
||||||
|
@ -785,7 +775,6 @@ Future<void> setup({
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletEditPage, WalletEditPageArguments, void>((arguments, _) {
|
getIt.registerFactoryParam<WalletEditPage, WalletEditPageArguments, void>((arguments, _) {
|
||||||
|
|
||||||
return WalletEditPage(
|
return WalletEditPage(
|
||||||
pageArguments: WalletEditPageArguments(
|
pageArguments: WalletEditPageArguments(
|
||||||
walletEditViewModel: getIt.get<WalletEditViewModel>(param1: arguments.walletListViewModel),
|
walletEditViewModel: getIt.get<WalletEditViewModel>(param1: arguments.walletListViewModel),
|
||||||
|
@ -884,8 +873,9 @@ Future<void> setup({
|
||||||
getIt.registerFactory(() => TrocadorProvidersViewModel(getIt.get<SettingsStore>()));
|
getIt.registerFactory(() => TrocadorProvidersViewModel(getIt.get<SettingsStore>()));
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
return OtherSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!,
|
return OtherSettingsViewModel(
|
||||||
getIt.get<SendViewModel>());});
|
getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!, getIt.get<SendViewModel>());
|
||||||
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
return SecuritySettingsViewModel(getIt.get<SettingsStore>());
|
return SecuritySettingsViewModel(getIt.get<SettingsStore>());
|
||||||
|
@ -893,7 +883,8 @@ Future<void> setup({
|
||||||
|
|
||||||
getIt.registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet!));
|
getIt.registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet!));
|
||||||
|
|
||||||
getIt.registerFactory<SeedSettingsViewModel>(() => SeedSettingsViewModel(getIt.get<AppStore>(), getIt.get<SeedSettingsStore>()));
|
getIt.registerFactory<SeedSettingsViewModel>(
|
||||||
|
() => SeedSettingsViewModel(getIt.get<AppStore>(), getIt.get<SeedSettingsStore>()));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletSeedPage, bool, void>((bool isWalletCreated, _) =>
|
getIt.registerFactoryParam<WalletSeedPage, bool, void>((bool isWalletCreated, _) =>
|
||||||
WalletSeedPage(getIt.get<WalletSeedViewModel>(), isNewWalletCreated: isWalletCreated));
|
WalletSeedPage(getIt.get<WalletSeedViewModel>(), isNewWalletCreated: isWalletCreated));
|
||||||
|
@ -1037,6 +1028,7 @@ Future<void> setup({
|
||||||
_unspentCoinsInfoSource,
|
_unspentCoinsInfoSource,
|
||||||
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
|
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
|
||||||
SettingsStoreBase.walletPasswordDirectInput,
|
SettingsStoreBase.walletPasswordDirectInput,
|
||||||
|
getIt.get<SettingsStore>().useMempoolFeeAPI,
|
||||||
);
|
);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return bitcoin!.createLitecoinWalletService(
|
return bitcoin!.createLitecoinWalletService(
|
||||||
|
@ -1044,16 +1036,22 @@ Future<void> setup({
|
||||||
_unspentCoinsInfoSource,
|
_unspentCoinsInfoSource,
|
||||||
getIt.get<SettingsStore>().mwebAlwaysScan,
|
getIt.get<SettingsStore>().mwebAlwaysScan,
|
||||||
SettingsStoreBase.walletPasswordDirectInput,
|
SettingsStoreBase.walletPasswordDirectInput,
|
||||||
|
getIt.get<SettingsStore>().useMempoolFeeAPI,
|
||||||
);
|
);
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
return ethereum!.createEthereumWalletService(
|
return ethereum!.createEthereumWalletService(
|
||||||
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource,
|
return bitcoinCash!.createBitcoinCashWalletService(
|
||||||
_unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
_walletInfoSource,
|
||||||
|
_unspentCoinsInfoSource,
|
||||||
|
SettingsStoreBase.walletPasswordDirectInput,
|
||||||
|
getIt.get<SettingsStore>().useMempoolFeeAPI,
|
||||||
|
);
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
return nano!.createNanoWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
return nano!.createNanoWalletService(
|
||||||
|
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
return polygon!.createPolygonWalletService(
|
return polygon!.createPolygonWalletService(
|
||||||
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
||||||
|
@ -1061,7 +1059,8 @@ Future<void> setup({
|
||||||
return solana!.createSolanaWalletService(
|
return solana!.createSolanaWalletService(
|
||||||
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return tron!.createTronWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
return tron!.createTronWalletService(
|
||||||
|
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
||||||
case WalletType.wownero:
|
case WalletType.wownero:
|
||||||
return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
|
@ -1100,40 +1099,36 @@ Future<void> setup({
|
||||||
param1: derivations,
|
param1: derivations,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
getIt.registerFactoryParam<TransactionDetailsViewModel, List<dynamic>, void>(
|
getIt.registerFactoryParam<TransactionDetailsViewModel, List<dynamic>, void>((params, _) {
|
||||||
(params, _) {
|
final transactionInfo = params[0] as TransactionInfo;
|
||||||
final transactionInfo = params[0] as TransactionInfo;
|
final canReplaceByFee = params[1] as bool? ?? false;
|
||||||
final canReplaceByFee = params[1] as bool? ?? false;
|
final wallet = getIt.get<AppStore>().wallet!;
|
||||||
final wallet = getIt.get<AppStore>().wallet!;
|
|
||||||
|
|
||||||
return TransactionDetailsViewModel(
|
return TransactionDetailsViewModel(
|
||||||
transactionInfo: transactionInfo,
|
transactionInfo: transactionInfo,
|
||||||
transactionDescriptionBox: _transactionDescriptionBox,
|
transactionDescriptionBox: _transactionDescriptionBox,
|
||||||
wallet: wallet,
|
wallet: wallet,
|
||||||
settingsStore: getIt.get<SettingsStore>(),
|
settingsStore: getIt.get<SettingsStore>(),
|
||||||
sendViewModel: getIt.get<SendViewModel>(),
|
sendViewModel: getIt.get<SendViewModel>(),
|
||||||
canReplaceByFee: canReplaceByFee,
|
canReplaceByFee: canReplaceByFee,
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
|
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
|
||||||
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
|
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
|
||||||
transactionDetailsViewModel: getIt.get<TransactionDetailsViewModel>(
|
transactionDetailsViewModel:
|
||||||
param1: [transactionInfo, false])));
|
getIt.get<TransactionDetailsViewModel>(param1: [transactionInfo, false])));
|
||||||
|
|
||||||
getIt.registerFactoryParam<RBFDetailsPage, List<dynamic>, void>(
|
getIt.registerFactoryParam<RBFDetailsPage, List<dynamic>, void>((params, _) {
|
||||||
(params, _) {
|
final transactionInfo = params[0] as TransactionInfo;
|
||||||
final transactionInfo = params[0] as TransactionInfo;
|
final txHex = params[1] as String;
|
||||||
final txHex = params[1] as String;
|
return RBFDetailsPage(
|
||||||
return RBFDetailsPage(
|
transactionDetailsViewModel: getIt.get<TransactionDetailsViewModel>(
|
||||||
transactionDetailsViewModel: getIt.get<TransactionDetailsViewModel>(
|
param1: [transactionInfo, true],
|
||||||
param1: [transactionInfo, true],
|
),
|
||||||
),
|
rawTransaction: txHex,
|
||||||
rawTransaction: txHex,
|
);
|
||||||
);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<NewWalletTypePage, NewWalletTypeArguments, void>(
|
getIt.registerFactoryParam<NewWalletTypePage, NewWalletTypeArguments, void>(
|
||||||
(newWalletTypeArguments, _) {
|
(newWalletTypeArguments, _) {
|
||||||
|
@ -1155,8 +1150,7 @@ Future<void> setup({
|
||||||
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
|
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
||||||
_transactionDescriptionBox,
|
_transactionDescriptionBox, getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
||||||
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
|
||||||
|
|
||||||
getIt.registerFactory(() => BackupViewModel(
|
getIt.registerFactory(() => BackupViewModel(
|
||||||
getIt.get<SecureStorage>(), getIt.get<SecretStore>(), getIt.get<BackupService>()));
|
getIt.get<SecureStorage>(), getIt.get<SecretStore>(), getIt.get<BackupService>()));
|
||||||
|
|
|
@ -114,7 +114,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
||||||
FiatCurrency.tur.raw: FiatCurrency.tur,
|
FiatCurrency.tur.raw: FiatCurrency.tur,
|
||||||
};
|
};
|
||||||
|
|
||||||
static FiatCurrency deserialize({required String raw}) => _all[raw]!;
|
static FiatCurrency deserialize({required String raw}) => _all[raw] ?? FiatCurrency.usd;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => other is FiatCurrency && other.raw == raw;
|
bool operator ==(Object other) => other is FiatCurrency && other.raw == raw;
|
||||||
|
|
|
@ -48,6 +48,7 @@ class PreferencesKey {
|
||||||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||||
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
||||||
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
|
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
|
||||||
|
static const silentPaymentsKeyRegistered = 'silentPaymentsKeyRegistered';
|
||||||
static const mwebCardDisplay = 'mwebCardDisplay';
|
static const mwebCardDisplay = 'mwebCardDisplay';
|
||||||
static const mwebEnabled = 'mwebEnabled';
|
static const mwebEnabled = 'mwebEnabled';
|
||||||
static const hasEnabledMwebBefore = 'hasEnabledMwebBefore';
|
static const hasEnabledMwebBefore = 'hasEnabledMwebBefore';
|
||||||
|
|
|
@ -16,7 +16,6 @@ import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||||
import 'package:cake_wallet/exchange/trade.dart';
|
import 'package:cake_wallet/exchange/trade.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/locales/locale.dart';
|
import 'package:cake_wallet/locales/locale.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
|
||||||
import 'package:cake_wallet/reactions/bootstrap.dart';
|
import 'package:cake_wallet/reactions/bootstrap.dart';
|
||||||
import 'package:cake_wallet/router.dart' as Router;
|
import 'package:cake_wallet/router.dart' as Router;
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
|
|
@ -10,7 +10,6 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/dropdown_item_
|
||||||
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||||
import 'package:cake_wallet/utils/show_bar.dart';
|
import 'package:cake_wallet/utils/show_bar.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
|
@ -100,6 +99,11 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
final selectedItem = dropDownItems.firstWhere(
|
||||||
|
(element) => element.isSelected,
|
||||||
|
orElse: () => dropDownItems.first,
|
||||||
|
);
|
||||||
|
|
||||||
return DropdownButton<DesktopDropdownItem>(
|
return DropdownButton<DesktopDropdownItem>(
|
||||||
items: dropDownItems
|
items: dropDownItems
|
||||||
.map(
|
.map(
|
||||||
|
@ -115,7 +119,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
dropdownColor: themeData.extension<CakeMenuTheme>()!.backgroundColor,
|
dropdownColor: themeData.extension<CakeMenuTheme>()!.backgroundColor,
|
||||||
style: TextStyle(color: themeData.extension<CakeTextTheme>()!.titleColor),
|
style: TextStyle(color: themeData.extension<CakeTextTheme>()!.titleColor),
|
||||||
selectedItemBuilder: (context) => dropDownItems.map((item) => item.child).toList(),
|
selectedItemBuilder: (context) => dropDownItems.map((item) => item.child).toList(),
|
||||||
value: dropDownItems.firstWhere((element) => element.isSelected),
|
value: selectedItem,
|
||||||
underline: const SizedBox(),
|
underline: const SizedBox(),
|
||||||
focusColor: Colors.transparent,
|
focusColor: Colors.transparent,
|
||||||
borderRadius: BorderRadius.circular(15.0),
|
borderRadius: BorderRadius.circular(15.0),
|
||||||
|
|
|
@ -72,24 +72,52 @@ class AddressPage extends BasePage {
|
||||||
|
|
||||||
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
|
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
|
||||||
|
|
||||||
return MergeSemantics(
|
return Row(
|
||||||
child: SizedBox(
|
mainAxisSize: MainAxisSize.min,
|
||||||
height: isMobileView ? 37 : 45,
|
children: [
|
||||||
width: isMobileView ? 37 : 45,
|
MergeSemantics(
|
||||||
child: ButtonTheme(
|
child: SizedBox(
|
||||||
minWidth: double.minPositive,
|
height: isMobileView ? 37 : 45,
|
||||||
child: Semantics(
|
width: isMobileView ? 37 : 45,
|
||||||
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
|
child: ButtonTheme(
|
||||||
child: TextButton(
|
minWidth: double.minPositive,
|
||||||
style: ButtonStyle(
|
child: Semantics(
|
||||||
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
|
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
|
||||||
|
),
|
||||||
|
onPressed: () => onClose(context),
|
||||||
|
child: !isMobileView ? _closeButton : _backButton,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () => onClose(context),
|
|
||||||
child: !isMobileView ? _closeButton : _backButton,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
MergeSemantics(
|
||||||
|
child: SizedBox(
|
||||||
|
height: isMobileView ? 37 : 45,
|
||||||
|
width: isMobileView ? 37 : 45,
|
||||||
|
child: ButtonTheme(
|
||||||
|
minWidth: double.minPositive,
|
||||||
|
child: Semantics(
|
||||||
|
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
|
||||||
|
),
|
||||||
|
onPressed: () => onClose(context),
|
||||||
|
child: Icon(
|
||||||
|
Icons.more_vert,
|
||||||
|
color: titleColor(context),
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,13 +178,13 @@ class AddressPage extends BasePage {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Observer(
|
child: Observer(
|
||||||
builder: (_) => QRWidget(
|
builder: (_) => QRWidget(
|
||||||
formKey: _formKey,
|
formKey: _formKey,
|
||||||
addressListViewModel: addressListViewModel,
|
addressListViewModel: addressListViewModel,
|
||||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||||
amountController: _amountController,
|
amountController: _amountController,
|
||||||
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
|
isLight: dashboardViewModel.settingsStore.currentTheme.type ==
|
||||||
ThemeType.light,
|
ThemeType.light,
|
||||||
))),
|
))),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
Observer(builder: (_) {
|
Observer(builder: (_) {
|
||||||
if (addressListViewModel.hasAddressList) {
|
if (addressListViewModel.hasAddressList) {
|
||||||
|
|
|
@ -1,27 +1,13 @@
|
||||||
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
|
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
|
||||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||||
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
|
||||||
import 'package:cake_wallet/themes/theme_base.dart';
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
import 'package:cake_wallet/utils/share_util.dart';
|
import 'package:cake_wallet/utils/share_util.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
|
||||||
import 'package:cw_core/wallet_type.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
||||||
import 'package:cake_wallet/routes.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
|
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
|
||||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||||
|
@ -116,13 +102,13 @@ class ReceivePage extends BasePage {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
||||||
child: QRWidget(
|
child: QRWidget(
|
||||||
addressListViewModel: addressListViewModel,
|
addressListViewModel: addressListViewModel,
|
||||||
formKey: _formKey,
|
formKey: _formKey,
|
||||||
heroTag: _heroTag,
|
heroTag: _heroTag,
|
||||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||||
amountController: _amountController,
|
amountController: _amountController,
|
||||||
isLight: currentTheme.type == ThemeType.light,
|
isLight: currentTheme.type == ThemeType.light,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AddressList(addressListViewModel: addressListViewModel),
|
AddressList(addressListViewModel: addressListViewModel),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
@ -37,7 +34,6 @@ class AddressList extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AddressListState extends State<AddressList> {
|
class _AddressListState extends State<AddressList> {
|
||||||
|
|
||||||
bool showHiddenAddresses = false;
|
bool showHiddenAddresses = false;
|
||||||
|
|
||||||
void _toggleHiddenAddresses() {
|
void _toggleHiddenAddresses() {
|
||||||
|
@ -131,9 +127,10 @@ class _AddressListState extends State<AddressList> {
|
||||||
showTrailingButton: widget.addressListViewModel.showAddManualAddresses,
|
showTrailingButton: widget.addressListViewModel.showAddManualAddresses,
|
||||||
showSearchButton: true,
|
showSearchButton: true,
|
||||||
onSearchCallback: updateItems,
|
onSearchCallback: updateItems,
|
||||||
trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
|
trailingButtonTap: () =>
|
||||||
updateItems(); // refresh the new address
|
Navigator.of(context).pushNamed(Routes.newSubaddress).then((value) {
|
||||||
}),
|
updateItems(); // refresh the new address
|
||||||
|
}),
|
||||||
trailingIcon: Icon(
|
trailingIcon: Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
size: 20,
|
size: 20,
|
||||||
|
@ -148,7 +145,8 @@ class _AddressListState extends State<AddressList> {
|
||||||
cell = Container();
|
cell = Container();
|
||||||
} else {
|
} else {
|
||||||
cell = Observer(builder: (_) {
|
cell = Observer(builder: (_) {
|
||||||
final isCurrent = item.address == widget.addressListViewModel.address.address && editable;
|
final isCurrent =
|
||||||
|
item.address == widget.addressListViewModel.address.address && editable;
|
||||||
final backgroundColor = isCurrent
|
final backgroundColor = isCurrent
|
||||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
|
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
|
||||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
|
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
|
||||||
|
@ -156,17 +154,17 @@ class _AddressListState extends State<AddressList> {
|
||||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
|
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
|
||||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
||||||
|
|
||||||
|
|
||||||
return AddressCell.fromItem(
|
return AddressCell.fromItem(
|
||||||
item,
|
item,
|
||||||
isCurrent: isCurrent,
|
isCurrent: isCurrent,
|
||||||
hasBalance: widget.addressListViewModel.isBalanceAvailable,
|
hasBalance: widget.addressListViewModel.isBalanceAvailable,
|
||||||
hasReceived: widget.addressListViewModel.isReceivedAvailable,
|
hasReceived: widget.addressListViewModel.isReceivedAvailable,
|
||||||
// hasReceived:
|
// hasReceived:
|
||||||
backgroundColor: (kDebugMode && item.isHidden) ?
|
backgroundColor: (kDebugMode && item.isHidden)
|
||||||
Theme.of(context).colorScheme.error :
|
? Theme.of(context).colorScheme.error
|
||||||
(kDebugMode && item.isManual) ? Theme.of(context).colorScheme.error.withBlue(255) :
|
: (kDebugMode && item.isManual)
|
||||||
backgroundColor,
|
? Theme.of(context).colorScheme.error.withBlue(255)
|
||||||
|
: backgroundColor,
|
||||||
textColor: textColor,
|
textColor: textColor,
|
||||||
onTap: (_) {
|
onTap: (_) {
|
||||||
if (widget.onSelect != null) {
|
if (widget.onSelect != null) {
|
||||||
|
@ -176,9 +174,11 @@ class _AddressListState extends State<AddressList> {
|
||||||
widget.addressListViewModel.setAddress(item);
|
widget.addressListViewModel.setAddress(item);
|
||||||
},
|
},
|
||||||
onEdit: editable
|
onEdit: editable
|
||||||
? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item).then((value) {
|
? () => Navigator.of(context)
|
||||||
updateItems(); // refresh the new address
|
.pushNamed(Routes.newSubaddress, arguments: item)
|
||||||
})
|
.then((value) {
|
||||||
|
updateItems(); // refresh the new address
|
||||||
|
})
|
||||||
: null,
|
: null,
|
||||||
isHidden: item.isHidden,
|
isHidden: item.isHidden,
|
||||||
onHide: () => _hideAddress(item),
|
onHide: () => _hideAddress(item),
|
||||||
|
@ -190,8 +190,8 @@ class _AddressListState extends State<AddressList> {
|
||||||
return index != 0
|
return index != 0
|
||||||
? cell
|
? cell
|
||||||
: ClipRRect(
|
: ClipRRect(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius:
|
||||||
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
|
BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)),
|
||||||
child: cell,
|
child: cell,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -202,5 +202,4 @@ class _AddressListState extends State<AddressList> {
|
||||||
await widget.addressListViewModel.toggleHideAddress(item);
|
await widget.addressListViewModel.toggleHideAddress(item);
|
||||||
updateItems();
|
updateItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,10 @@ class WalletRestorePage extends BasePage {
|
||||||
_validateOnChange(isPolyseed: isPolyseed);
|
_validateOnChange(isPolyseed: isPolyseed);
|
||||||
},
|
},
|
||||||
displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
|
displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
|
||||||
onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password,
|
onPasswordChange: (String password) =>
|
||||||
onRepeatedPasswordChange: (String repeatedPassword) => walletRestoreViewModel.repeatedWalletPassword = repeatedPassword));
|
walletRestoreViewModel.walletPassword = password,
|
||||||
|
onRepeatedPasswordChange: (String repeatedPassword) =>
|
||||||
|
walletRestoreViewModel.repeatedWalletPassword = repeatedPassword));
|
||||||
break;
|
break;
|
||||||
case WalletRestoreMode.keys:
|
case WalletRestoreMode.keys:
|
||||||
_pages.add(WalletRestoreFromKeysFrom(
|
_pages.add(WalletRestoreFromKeysFrom(
|
||||||
|
@ -69,8 +71,10 @@ class WalletRestorePage extends BasePage {
|
||||||
},
|
},
|
||||||
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
|
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
|
||||||
displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
|
displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
|
||||||
onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password,
|
onPasswordChange: (String password) =>
|
||||||
onRepeatedPasswordChange: (String repeatedPassword) => walletRestoreViewModel.repeatedWalletPassword = repeatedPassword,
|
walletRestoreViewModel.walletPassword = password,
|
||||||
|
onRepeatedPasswordChange: (String repeatedPassword) =>
|
||||||
|
walletRestoreViewModel.repeatedWalletPassword = repeatedPassword,
|
||||||
onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
|
onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -105,6 +109,7 @@ class WalletRestorePage extends BasePage {
|
||||||
// DerivationType derivationType = DerivationType.unknown;
|
// DerivationType derivationType = DerivationType.unknown;
|
||||||
// String? derivationPath = null;
|
// String? derivationPath = null;
|
||||||
DerivationInfo? derivationInfo;
|
DerivationInfo? derivationInfo;
|
||||||
|
List<DerivationInfo>? derivations;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Function(BuildContext)? get pushToNextWidget => (context) {
|
Function(BuildContext)? get pushToNextWidget => (context) {
|
||||||
|
@ -342,6 +347,7 @@ class WalletRestorePage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials['derivationInfo'] = this.derivationInfo;
|
credentials['derivationInfo'] = this.derivationInfo;
|
||||||
|
credentials['derivations'] = this.derivations;
|
||||||
credentials['walletType'] = walletRestoreViewModel.type;
|
credentials['walletType'] = walletRestoreViewModel.type;
|
||||||
return credentials;
|
return credentials;
|
||||||
}
|
}
|
||||||
|
@ -379,39 +385,43 @@ class WalletRestorePage extends BasePage {
|
||||||
|
|
||||||
walletRestoreViewModel.state = IsExecutingState();
|
walletRestoreViewModel.state = IsExecutingState();
|
||||||
|
|
||||||
DerivationInfo? dInfo;
|
|
||||||
|
|
||||||
// get info about the different derivations:
|
// get info about the different derivations:
|
||||||
List<DerivationInfo> derivations =
|
List<DerivationInfo> derivations =
|
||||||
await walletRestoreViewModel.getDerivationInfo(_credentials());
|
await walletRestoreViewModel.getDerivationInfo(_credentials());
|
||||||
|
|
||||||
int derivationsWithHistory = 0;
|
if (walletRestoreViewModel.type == WalletType.nano) {
|
||||||
int derivationWithHistoryIndex = 0;
|
DerivationInfo? dInfo;
|
||||||
for (int i = 0; i < derivations.length; i++) {
|
|
||||||
if (derivations[i].transactionsCount > 0) {
|
int derivationsWithHistory = 0;
|
||||||
derivationsWithHistory++;
|
int derivationWithHistoryIndex = 0;
|
||||||
derivationWithHistoryIndex = i;
|
for (int i = 0; i < derivations.length; i++) {
|
||||||
|
if (derivations[i].transactionsCount > 0) {
|
||||||
|
derivationsWithHistory++;
|
||||||
|
derivationWithHistoryIndex = i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (derivationsWithHistory > 1) {
|
if (derivationsWithHistory > 1) {
|
||||||
dInfo = await Navigator.of(context).pushNamed(
|
dInfo = await Navigator.of(context).pushNamed(
|
||||||
Routes.restoreWalletChooseDerivation,
|
Routes.restoreWalletChooseDerivation,
|
||||||
arguments: derivations,
|
arguments: derivations,
|
||||||
) as DerivationInfo?;
|
) as DerivationInfo?;
|
||||||
} else if (derivationsWithHistory == 1) {
|
} else if (derivationsWithHistory == 1) {
|
||||||
dInfo = derivations[derivationWithHistoryIndex];
|
dInfo = derivations[derivationWithHistoryIndex];
|
||||||
} else if (derivations.length == 1) {
|
} else if (derivations.length == 1) {
|
||||||
// we only return 1 derivation if we're pretty sure we know which one to use:
|
// we only return 1 derivation if we're pretty sure we know which one to use:
|
||||||
dInfo = derivations.first;
|
dInfo = derivations.first;
|
||||||
|
} else {
|
||||||
|
// if we have multiple possible derivations, and none (or multiple) have histories
|
||||||
|
// we just default to the most common one:
|
||||||
|
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.derivationInfo = dInfo;
|
||||||
} else {
|
} else {
|
||||||
// if we have multiple possible derivations, and none (or multiple) have histories
|
this.derivations = derivations;
|
||||||
// we just default to the most common one:
|
|
||||||
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.derivationInfo = dInfo;
|
|
||||||
|
|
||||||
await walletRestoreViewModel.create(options: _credentials());
|
await walletRestoreViewModel.create(options: _credentials());
|
||||||
seedSettingsViewModel.setPassphrase(null);
|
seedSettingsViewModel.setPassphrase(null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -37,6 +37,13 @@ class SilentPaymentsSettingsPage extends BasePage {
|
||||||
_silentPaymentsSettingsViewModel.setSilentPaymentsAlwaysScan(value);
|
_silentPaymentsSettingsViewModel.setSilentPaymentsAlwaysScan(value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
SettingsSwitcherCell(
|
||||||
|
title: S.current.silent_payments_register_key,
|
||||||
|
value: _silentPaymentsSettingsViewModel.silentPaymentsKeyRegistered,
|
||||||
|
onValueChange: (_, bool value) {
|
||||||
|
_silentPaymentsSettingsViewModel.registerSilentPaymentsKey(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
SettingsCellWithArrow(
|
SettingsCellWithArrow(
|
||||||
title: S.current.silent_payments_scanning,
|
title: S.current.silent_payments_scanning,
|
||||||
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
|
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
|
||||||
|
|
|
@ -52,7 +52,9 @@ class BottomSheetListenerState extends State<BottomSheetListener> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
item.completer.complete(value);
|
if (!item.completer.isCompleted) {
|
||||||
|
item.completer.complete(value);
|
||||||
|
}
|
||||||
widget.bottomSheetService.resetCurrentSheet();
|
widget.bottomSheetService.resetCurrentSheet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ class CakeImageWidget extends StatelessWidget {
|
||||||
imageUrl!,
|
imageUrl!,
|
||||||
height: height,
|
height: height,
|
||||||
width: width,
|
width: width,
|
||||||
|
errorBuilder: (_, __, ___) => Icon(Icons.error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ class CakeImageWidget extends StatelessWidget {
|
||||||
imageUrl!,
|
imageUrl!,
|
||||||
height: height,
|
height: height,
|
||||||
width: width,
|
width: width,
|
||||||
|
placeholderBuilder: (_) => Icon(Icons.error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ class _ServicesUpdatesWidgetState extends State<ServicesUpdatesWidget> {
|
||||||
"assets/images/notification_icon.svg",
|
"assets/images/notification_icon.svg",
|
||||||
color: Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
|
color: Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
|
||||||
width: 30,
|
width: 30,
|
||||||
|
placeholderBuilder: (_) => Icon(Icons.error),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -136,6 +137,7 @@ class _ServicesUpdatesWidgetState extends State<ServicesUpdatesWidget> {
|
||||||
"assets/images/notification_icon.svg",
|
"assets/images/notification_icon.svg",
|
||||||
color: Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
|
color: Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
|
||||||
width: 30,
|
width: 30,
|
||||||
|
placeholderBuilder: (_) => Icon(Icons.error),
|
||||||
),
|
),
|
||||||
if (state.hasData && state.data!.hasUpdates && !wasOpened)
|
if (state.hasData && state.data!.hasUpdates && !wasOpened)
|
||||||
Container(
|
Container(
|
||||||
|
|
|
@ -91,8 +91,9 @@ abstract class TransactionFilterStoreBase with Store {
|
||||||
(displayOutgoing && item.transaction.direction == TransactionDirection.outgoing) ||
|
(displayOutgoing && item.transaction.direction == TransactionDirection.outgoing) ||
|
||||||
(displayIncoming &&
|
(displayIncoming &&
|
||||||
item.transaction.direction == TransactionDirection.incoming &&
|
item.transaction.direction == TransactionDirection.incoming &&
|
||||||
!bitcoin!.txIsReceivedSilentPayment(item.transaction)) ||
|
!(bitcoin?.txIsReceivedSilentPayment(item.transaction) ?? false)) ||
|
||||||
(displaySilentPayments && bitcoin!.txIsReceivedSilentPayment(item.transaction));
|
(displaySilentPayments &&
|
||||||
|
(bitcoin?.txIsReceivedSilentPayment(item.transaction) ?? false));
|
||||||
} else if (item is AnonpayTransactionListItem) {
|
} else if (item is AnonpayTransactionListItem) {
|
||||||
allowed = displayIncoming;
|
allowed = displayIncoming;
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
required this.customBitcoinFeeRate,
|
required this.customBitcoinFeeRate,
|
||||||
required this.silentPaymentsCardDisplay,
|
required this.silentPaymentsCardDisplay,
|
||||||
required this.silentPaymentsAlwaysScan,
|
required this.silentPaymentsAlwaysScan,
|
||||||
|
required this.silentPaymentsKeyRegistered,
|
||||||
required this.mwebAlwaysScan,
|
required this.mwebAlwaysScan,
|
||||||
required this.mwebCardDisplay,
|
required this.mwebCardDisplay,
|
||||||
required this.mwebEnabled,
|
required this.mwebEnabled,
|
||||||
|
@ -344,8 +345,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => bitcoinSeedType,
|
(_) => bitcoinSeedType,
|
||||||
(BitcoinSeedType bitcoinSeedType) => sharedPreferences.setInt(
|
(BitcoinSeedType bitcoinSeedType) =>
|
||||||
PreferencesKey.bitcoinSeedType, bitcoinSeedType.raw));
|
sharedPreferences.setInt(PreferencesKey.bitcoinSeedType, bitcoinSeedType.raw));
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => nanoSeedType,
|
(_) => nanoSeedType,
|
||||||
|
@ -428,8 +429,10 @@ abstract class SettingsStoreBase with Store {
|
||||||
reaction((_) => useTronGrid,
|
reaction((_) => useTronGrid,
|
||||||
(bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
|
(bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
|
||||||
|
|
||||||
reaction((_) => useMempoolFeeAPI,
|
reaction(
|
||||||
(bool useMempoolFeeAPI) => _sharedPreferences.setBool(PreferencesKey.useMempoolFeeAPI, useMempoolFeeAPI));
|
(_) => useMempoolFeeAPI,
|
||||||
|
(bool useMempoolFeeAPI) =>
|
||||||
|
_sharedPreferences.setBool(PreferencesKey.useMempoolFeeAPI, useMempoolFeeAPI));
|
||||||
|
|
||||||
reaction((_) => defaultNanoRep,
|
reaction((_) => defaultNanoRep,
|
||||||
(String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep));
|
(String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep));
|
||||||
|
@ -559,6 +562,11 @@ abstract class SettingsStoreBase with Store {
|
||||||
(bool silentPaymentsAlwaysScan) => _sharedPreferences.setBool(
|
(bool silentPaymentsAlwaysScan) => _sharedPreferences.setBool(
|
||||||
PreferencesKey.silentPaymentsAlwaysScan, silentPaymentsAlwaysScan));
|
PreferencesKey.silentPaymentsAlwaysScan, silentPaymentsAlwaysScan));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => silentPaymentsKeyRegistered,
|
||||||
|
(bool silentPaymentsKeyRegistered) => _sharedPreferences.setBool(
|
||||||
|
PreferencesKey.silentPaymentsKeyRegistered, silentPaymentsKeyRegistered));
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => mwebAlwaysScan,
|
(_) => mwebAlwaysScan,
|
||||||
(bool mwebAlwaysScan) =>
|
(bool mwebAlwaysScan) =>
|
||||||
|
@ -790,6 +798,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool silentPaymentsAlwaysScan;
|
bool silentPaymentsAlwaysScan;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool silentPaymentsKeyRegistered;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool mwebAlwaysScan;
|
bool mwebAlwaysScan;
|
||||||
|
|
||||||
|
@ -959,6 +970,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
||||||
final silentPaymentsAlwaysScan =
|
final silentPaymentsAlwaysScan =
|
||||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
|
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
|
||||||
|
final silentPaymentsKeyRegistered =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.silentPaymentsKeyRegistered) ?? false;
|
||||||
final mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false;
|
final mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false;
|
||||||
final mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true;
|
final mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true;
|
||||||
final mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
|
final mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
|
||||||
|
@ -1230,6 +1243,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
customBitcoinFeeRate: customBitcoinFeeRate,
|
customBitcoinFeeRate: customBitcoinFeeRate,
|
||||||
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
|
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
|
||||||
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
|
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
|
||||||
|
silentPaymentsKeyRegistered: silentPaymentsKeyRegistered,
|
||||||
mwebAlwaysScan: mwebAlwaysScan,
|
mwebAlwaysScan: mwebAlwaysScan,
|
||||||
mwebCardDisplay: mwebCardDisplay,
|
mwebCardDisplay: mwebCardDisplay,
|
||||||
mwebEnabled: mwebEnabled,
|
mwebEnabled: mwebEnabled,
|
||||||
|
@ -1396,6 +1410,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
||||||
silentPaymentsAlwaysScan =
|
silentPaymentsAlwaysScan =
|
||||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
|
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
|
||||||
|
silentPaymentsKeyRegistered =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.silentPaymentsKeyRegistered) ?? false;
|
||||||
mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false;
|
mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false;
|
||||||
mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true;
|
mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true;
|
||||||
mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
|
mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
|
||||||
|
@ -1658,7 +1674,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
deviceName = windowsInfo.productName;
|
deviceName = windowsInfo.productName;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
print('likely digitalProductId is null wait till https://github.com/fluttercommunity/plus_plugins/pull/3188 is merged');
|
print(
|
||||||
|
'likely digitalProductId is null wait till https://github.com/fluttercommunity/plus_plugins/pull/3188 is merged');
|
||||||
deviceName = "Windows Device";
|
deviceName = "Windows Device";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,14 @@ class ExceptionHandler {
|
||||||
|
|
||||||
await _addDeviceInfo(_file!);
|
await _addDeviceInfo(_file!);
|
||||||
|
|
||||||
|
// Check if a mail client is available
|
||||||
|
final bool canSend = await FlutterMailer.canSendMail();
|
||||||
|
|
||||||
|
if (Platform.isIOS && !canSend) {
|
||||||
|
debugPrint('Mail app is not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final MailOptions mailOptions = MailOptions(
|
final MailOptions mailOptions = MailOptions(
|
||||||
subject: 'Mobile App Issue',
|
subject: 'Mobile App Issue',
|
||||||
recipients: ['support@cakewallet.com'],
|
recipients: ['support@cakewallet.com'],
|
||||||
|
|
|
@ -53,8 +53,18 @@ class ImageUtil {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return isSvg
|
return isSvg
|
||||||
? SvgPicture.asset(imagePath, height: _height, width: _width)
|
? SvgPicture.asset(
|
||||||
: Image.asset(imagePath, height: _height, width: _width);
|
imagePath,
|
||||||
|
height: _height,
|
||||||
|
width: _width,
|
||||||
|
placeholderBuilder: (_) => Icon(Icons.error),
|
||||||
|
)
|
||||||
|
: Image.asset(
|
||||||
|
imagePath,
|
||||||
|
height: _height,
|
||||||
|
width: _width,
|
||||||
|
errorBuilder: (_, __, ___) => Icon(Icons.error),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,11 +90,12 @@ abstract class DashboardViewModelBase with Store {
|
||||||
value: () => transactionFilterStore.displayOutgoing,
|
value: () => transactionFilterStore.displayOutgoing,
|
||||||
caption: S.current.outgoing,
|
caption: S.current.outgoing,
|
||||||
onChanged: transactionFilterStore.toggleOutgoing),
|
onChanged: transactionFilterStore.toggleOutgoing),
|
||||||
FilterItem(
|
if (appStore.wallet!.type == WalletType.bitcoin)
|
||||||
value: () => transactionFilterStore.displaySilentPayments,
|
FilterItem(
|
||||||
caption: S.current.silent_payments,
|
value: () => transactionFilterStore.displaySilentPayments,
|
||||||
onChanged: transactionFilterStore.toggleSilentPayments,
|
caption: S.current.silent_payments,
|
||||||
),
|
onChanged: transactionFilterStore.toggleSilentPayments,
|
||||||
|
),
|
||||||
// FilterItem(
|
// FilterItem(
|
||||||
// value: () => false,
|
// value: () => false,
|
||||||
// caption: S.current.transactions_by_date,
|
// caption: S.current.transactions_by_date,
|
||||||
|
@ -435,7 +436,10 @@ abstract class DashboardViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid) && !wallet.isHardwareWallet;
|
bool get hasMweb =>
|
||||||
|
wallet.type == WalletType.litecoin &&
|
||||||
|
(Platform.isIOS || Platform.isAndroid) &&
|
||||||
|
!wallet.isHardwareWallet;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebEnabled;
|
bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebEnabled;
|
||||||
|
|
|
@ -38,7 +38,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
||||||
wif = '',
|
wif = '',
|
||||||
address = '',
|
address = '',
|
||||||
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
|
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
|
||||||
type: type, isRecovery: true);
|
type: type, isRecovery: true);
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
int height;
|
int height;
|
||||||
|
@ -113,21 +113,12 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
||||||
);
|
);
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
|
||||||
final derivationInfoList = await getDerivationInfoFromQRCredentials(restoreWallet);
|
|
||||||
DerivationInfo derivationInfo;
|
|
||||||
if (derivationInfoList.isEmpty) {
|
|
||||||
derivationInfo = getDefaultCreateDerivation()!;
|
|
||||||
} else {
|
|
||||||
derivationInfo = derivationInfoList.first;
|
|
||||||
}
|
|
||||||
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
|
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
|
||||||
name: name,
|
name: name,
|
||||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||||
password: password,
|
password: password,
|
||||||
passphrase: restoreWallet.passphrase,
|
passphrase: restoreWallet.passphrase,
|
||||||
derivationType: derivationInfo.derivationType!,
|
derivations: [],
|
||||||
derivationPath: derivationInfo.derivationPath!,
|
|
||||||
);
|
);
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
|
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
|
||||||
|
@ -144,8 +135,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
||||||
passphrase: restoreWallet.passphrase,
|
passphrase: restoreWallet.passphrase,
|
||||||
);
|
);
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
final derivationInfo =
|
final derivationInfo = (await getDerivationInfoFromQRCredentials(restoreWallet)).first;
|
||||||
(await getDerivationInfoFromQRCredentials(restoreWallet)).first;
|
|
||||||
return nano!.createNanoRestoreWalletFromSeedCredentials(
|
return nano!.createNanoRestoreWalletFromSeedCredentials(
|
||||||
name: name,
|
name: name,
|
||||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||||
|
@ -190,8 +180,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials,
|
Future<WalletBase> processFromRestoredWallet(
|
||||||
RestoredWallet restoreWallet) async {
|
WalletCredentials credentials, RestoredWallet restoreWallet) async {
|
||||||
try {
|
try {
|
||||||
switch (restoreWallet.restoreMode) {
|
switch (restoreWallet.restoreMode) {
|
||||||
case WalletRestoreMode.keys:
|
case WalletRestoreMode.keys:
|
||||||
|
|
|
@ -400,7 +400,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
final updatedOutputs = bitcoin!.updateOutputs(pendingTransaction!, outputs);
|
final updatedOutputs = bitcoin!.updateOutputs(pendingTransaction!, outputs);
|
||||||
|
|
||||||
if (outputs.length == updatedOutputs.length) {
|
if (outputs.length == updatedOutputs.length) {
|
||||||
outputs = ObservableList.of(updatedOutputs);
|
outputs.clear();
|
||||||
|
outputs.addAll(updatedOutputs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,9 @@ abstract class SilentPaymentsSettingsViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get silentPaymentsAlwaysScan => _settingsStore.silentPaymentsAlwaysScan;
|
bool get silentPaymentsAlwaysScan => _settingsStore.silentPaymentsAlwaysScan;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get silentPaymentsKeyRegistered => _settingsStore.silentPaymentsKeyRegistered;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setSilentPaymentsCardDisplay(bool value) {
|
void setSilentPaymentsCardDisplay(bool value) {
|
||||||
_settingsStore.silentPaymentsCardDisplay = value;
|
_settingsStore.silentPaymentsCardDisplay = value;
|
||||||
|
@ -30,4 +33,10 @@ abstract class SilentPaymentsSettingsViewModelBase with Store {
|
||||||
_settingsStore.silentPaymentsAlwaysScan = value;
|
_settingsStore.silentPaymentsAlwaysScan = value;
|
||||||
if (value) bitcoin!.setScanningActive(_wallet, true);
|
if (value) bitcoin!.setScanningActive(_wallet, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void registerSilentPaymentsKey(bool value) {
|
||||||
|
_settingsStore.silentPaymentsKeyRegistered = value;
|
||||||
|
bitcoin!.registerSilentPaymentsKey(_wallet, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,18 +83,19 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showRecipientAddress && !isRecipientAddressShown) {
|
final descriptionKey =
|
||||||
try {
|
'${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||||
final recipientAddress = transactionDescriptionBox.values
|
final description = transactionDescriptionBox.values.firstWhere(
|
||||||
.firstWhere((val) => val.id == transactionInfo.txHash)
|
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||||
.recipientAddress;
|
orElse: () => TransactionDescription(id: descriptionKey));
|
||||||
|
|
||||||
if (recipientAddress?.isNotEmpty ?? false) {
|
if (showRecipientAddress && !isRecipientAddressShown) {
|
||||||
items.add(StandartListItem(
|
final recipientAddress = description.recipientAddress;
|
||||||
title: S.current.transaction_details_recipient_address, value: recipientAddress!));
|
|
||||||
}
|
if (recipientAddress?.isNotEmpty ?? false) {
|
||||||
} catch (_) {
|
items.add(StandartListItem(
|
||||||
// FIX-ME: Unhandled exception
|
title: S.current.transaction_details_recipient_address,
|
||||||
|
value: recipientAddress!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,12 +111,6 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
|
||||||
|
|
||||||
final description = transactionDescriptionBox.values.firstWhere(
|
|
||||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
|
||||||
orElse: () => TransactionDescription(id: descriptionKey));
|
|
||||||
|
|
||||||
items.add(TextFieldListItem(
|
items.add(TextFieldListItem(
|
||||||
title: S.current.note_tap_to_change,
|
title: S.current.note_tap_to_change,
|
||||||
value: description.note,
|
value: description.note,
|
||||||
|
|
|
@ -101,6 +101,7 @@ abstract class WalletCreationVMBase with Store {
|
||||||
address: '',
|
address: '',
|
||||||
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
|
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
|
||||||
derivationInfo: credentials.derivationInfo ?? getDefaultCreateDerivation(),
|
derivationInfo: credentials.derivationInfo ?? getDefaultCreateDerivation(),
|
||||||
|
derivations: credentials.derivations,
|
||||||
hardwareWalletType: credentials.hardwareWalletType,
|
hardwareWalletType: credentials.hardwareWalletType,
|
||||||
parentAddress: credentials.parentAddress,
|
parentAddress: credentials.parentAddress,
|
||||||
);
|
);
|
||||||
|
@ -200,15 +201,36 @@ abstract class WalletCreationVMBase with Store {
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
final derivationList = await bitcoin!.getDerivationsFromMnemonic(
|
final bitcoinDerivations = await bitcoin!.getDerivationsFromMnemonic(
|
||||||
mnemonic: restoreWallet.mnemonicSeed!,
|
mnemonic: restoreWallet.mnemonicSeed!,
|
||||||
node: node,
|
node: node,
|
||||||
passphrase: restoreWallet.passphrase,
|
passphrase: restoreWallet.passphrase,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (derivationList.firstOrNull?.transactionsCount == 0 && derivationList.length > 1)
|
List<DerivationInfo> list = [];
|
||||||
return [];
|
for (var derivation in bitcoinDerivations) {
|
||||||
return derivationList;
|
if (derivation.derivationType == DerivationType.electrum) {
|
||||||
|
list.add(
|
||||||
|
DerivationInfo(
|
||||||
|
derivationType: DerivationType.electrum,
|
||||||
|
derivationPath: "m/0'",
|
||||||
|
description: "Electrum",
|
||||||
|
scriptType: "p2wpkh",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
list.add(
|
||||||
|
DerivationInfo(
|
||||||
|
derivationType: DerivationType.bip39,
|
||||||
|
derivationPath: "m/84'/0'/0'",
|
||||||
|
description: "Standard BIP84 native segwit",
|
||||||
|
scriptType: "p2wpkh",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
return nanoUtil!.getDerivationsFromMnemonic(
|
return nanoUtil!.getDerivationsFromMnemonic(
|
||||||
|
|
|
@ -91,6 +91,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
final height = options['height'] as int? ?? 0;
|
final height = options['height'] as int? ?? 0;
|
||||||
name = options['name'] as String;
|
name = options['name'] as String;
|
||||||
DerivationInfo? derivationInfo = options["derivationInfo"] as DerivationInfo?;
|
DerivationInfo? derivationInfo = options["derivationInfo"] as DerivationInfo?;
|
||||||
|
List<DerivationInfo>? derivations = options["derivations"] as List<DerivationInfo>?;
|
||||||
|
|
||||||
if (mode == WalletRestoreMode.seed) {
|
if (mode == WalletRestoreMode.seed) {
|
||||||
final seed = options['seed'] as String;
|
final seed = options['seed'] as String;
|
||||||
|
@ -105,8 +106,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
mnemonic: seed,
|
mnemonic: seed,
|
||||||
password: password,
|
password: password,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
derivationType: derivationInfo!.derivationType!,
|
derivations: derivations,
|
||||||
derivationPath: derivationInfo.derivationPath!,
|
|
||||||
);
|
);
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return haven!.createHavenRestoreWalletFromSeedCredentials(
|
return haven!.createHavenRestoreWalletFromSeedCredentials(
|
||||||
|
@ -256,11 +256,36 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
String? mnemonic = credentials['seed'] as String?;
|
String? mnemonic = credentials['seed'] as String?;
|
||||||
String? passphrase = credentials['passphrase'] as String?;
|
String? passphrase = credentials['passphrase'] as String?;
|
||||||
return bitcoin!.getDerivationsFromMnemonic(
|
final bitcoinDerivations = await bitcoin!.getDerivationsFromMnemonic(
|
||||||
mnemonic: mnemonic!,
|
mnemonic: mnemonic!,
|
||||||
node: node,
|
node: node,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
List<DerivationInfo> list = [];
|
||||||
|
for (var derivation in bitcoinDerivations) {
|
||||||
|
if (derivation.derivationType.toString().endsWith("electrum")) {
|
||||||
|
list.add(
|
||||||
|
DerivationInfo(
|
||||||
|
derivationType: DerivationType.electrum,
|
||||||
|
derivationPath: "m/0'",
|
||||||
|
description: "Electrum",
|
||||||
|
scriptType: "p2wpkh",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
list.add(
|
||||||
|
DerivationInfo(
|
||||||
|
derivationType: DerivationType.bip39,
|
||||||
|
derivationPath: "m/84'/0'/0'",
|
||||||
|
description: "Standard BIP84 native segwit",
|
||||||
|
scriptType: "p2wpkh",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
String? mnemonic = credentials['seed'] as String?;
|
String? mnemonic = credentials['seed'] as String?;
|
||||||
String? seedKey = credentials['private_key'] as String?;
|
String? seedKey = credentials['private_key'] as String?;
|
||||||
|
|
|
@ -133,8 +133,8 @@ dependency_overrides:
|
||||||
protobuf: ^3.1.0
|
protobuf: ^3.1.0
|
||||||
bitcoin_base:
|
bitcoin_base:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_base
|
url: https://github.com/cake-tech/bitcoin_base.git
|
||||||
ref: cake-update-v9
|
ref: cake-update-v15
|
||||||
ffi: 2.1.0
|
ffi: 2.1.0
|
||||||
|
|
||||||
flutter_icons:
|
flutter_icons:
|
||||||
|
|
|
@ -709,6 +709,7 @@
|
||||||
"silent_payments_always_scan": "حدد المدفوعات الصامتة دائمًا المسح الضوئي",
|
"silent_payments_always_scan": "حدد المدفوعات الصامتة دائمًا المسح الضوئي",
|
||||||
"silent_payments_disclaimer": "العناوين الجديدة ليست هويات جديدة. إنها إعادة استخدام هوية موجودة مع ملصق مختلف.",
|
"silent_payments_disclaimer": "العناوين الجديدة ليست هويات جديدة. إنها إعادة استخدام هوية موجودة مع ملصق مختلف.",
|
||||||
"silent_payments_display_card": "عرض بطاقة المدفوعات الصامتة",
|
"silent_payments_display_card": "عرض بطاقة المدفوعات الصامتة",
|
||||||
|
"silent_payments_register_key": "سجل عرض مفتاح المسح الأسرع",
|
||||||
"silent_payments_scan_from_date": "فحص من التاريخ",
|
"silent_payments_scan_from_date": "فحص من التاريخ",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "يرجى إدخال ارتفاع الكتلة الذي تريد بدء المسح الضوئي للمدفوعات الصامتة الواردة ، أو استخدام التاريخ بدلاً من ذلك. يمكنك اختيار ما إذا كانت المحفظة تواصل مسح كل كتلة ، أو تتحقق فقط من الارتفاع المحدد.",
|
"silent_payments_scan_from_date_or_blockheight": "يرجى إدخال ارتفاع الكتلة الذي تريد بدء المسح الضوئي للمدفوعات الصامتة الواردة ، أو استخدام التاريخ بدلاً من ذلك. يمكنك اختيار ما إذا كانت المحفظة تواصل مسح كل كتلة ، أو تتحقق فقط من الارتفاع المحدد.",
|
||||||
"silent_payments_scan_from_height": "فحص من ارتفاع الكتلة",
|
"silent_payments_scan_from_height": "فحص من ارتفاع الكتلة",
|
||||||
|
|
|
@ -709,6 +709,7 @@
|
||||||
"silent_payments_always_scan": "Задайте мълчаливи плащания винаги сканиране",
|
"silent_payments_always_scan": "Задайте мълчаливи плащания винаги сканиране",
|
||||||
"silent_payments_disclaimer": "Новите адреси не са нови идентичности. Това е повторна употреба на съществуваща идентичност с различен етикет.",
|
"silent_payments_disclaimer": "Новите адреси не са нови идентичности. Това е повторна употреба на съществуваща идентичност с различен етикет.",
|
||||||
"silent_payments_display_card": "Показване на безшумни плащания карта",
|
"silent_payments_display_card": "Показване на безшумни плащания карта",
|
||||||
|
"silent_payments_register_key": "Регистрирайте ключа за преглед на по -бързото сканиране",
|
||||||
"silent_payments_scan_from_date": "Сканиране от дата",
|
"silent_payments_scan_from_date": "Сканиране от дата",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Моля, въведете височината на блока, която искате да започнете да сканирате за входящи безшумни плащания, или вместо това използвайте датата. Можете да изберете дали портфейлът продължава да сканира всеки блок или проверява само определената височина.",
|
"silent_payments_scan_from_date_or_blockheight": "Моля, въведете височината на блока, която искате да започнете да сканирате за входящи безшумни плащания, или вместо това използвайте датата. Можете да изберете дали портфейлът продължава да сканира всеки блок или проверява само определената височина.",
|
||||||
"silent_payments_scan_from_height": "Сканиране от височината на блока",
|
"silent_payments_scan_from_height": "Сканиране от височината на блока",
|
||||||
|
|
|
@ -709,6 +709,7 @@
|
||||||
"silent_payments_always_scan": "Nastavit tiché platby vždy skenování",
|
"silent_payments_always_scan": "Nastavit tiché platby vždy skenování",
|
||||||
"silent_payments_disclaimer": "Nové adresy nejsou nové identity. Je to opětovné použití existující identity s jiným štítkem.",
|
"silent_payments_disclaimer": "Nové adresy nejsou nové identity. Je to opětovné použití existující identity s jiným štítkem.",
|
||||||
"silent_payments_display_card": "Zobrazit kartu Silent Payments",
|
"silent_payments_display_card": "Zobrazit kartu Silent Payments",
|
||||||
|
"silent_payments_register_key": "Zobrazení zaregistrujte klíč pro rychlejší skenování",
|
||||||
"silent_payments_scan_from_date": "Skenovat od data",
|
"silent_payments_scan_from_date": "Skenovat od data",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Zadejte výšku bloku, kterou chcete začít skenovat, zda jsou přicházející tiché platby, nebo místo toho použijte datum. Můžete si vybrat, zda peněženka pokračuje v skenování každého bloku nebo zkontroluje pouze zadanou výšku.",
|
"silent_payments_scan_from_date_or_blockheight": "Zadejte výšku bloku, kterou chcete začít skenovat, zda jsou přicházející tiché platby, nebo místo toho použijte datum. Můžete si vybrat, zda peněženka pokračuje v skenování každého bloku nebo zkontroluje pouze zadanou výšku.",
|
||||||
"silent_payments_scan_from_height": "Skenování z výšky bloku",
|
"silent_payments_scan_from_height": "Skenování z výšky bloku",
|
||||||
|
|
|
@ -710,6 +710,7 @@
|
||||||
"silent_payments_always_scan": "Setzen Sie stille Zahlungen immer scannen",
|
"silent_payments_always_scan": "Setzen Sie stille Zahlungen immer scannen",
|
||||||
"silent_payments_disclaimer": "Neue Adressen sind keine neuen Identitäten. Es ist eine Wiederverwendung einer bestehenden Identität mit einem anderen Etikett.",
|
"silent_payments_disclaimer": "Neue Adressen sind keine neuen Identitäten. Es ist eine Wiederverwendung einer bestehenden Identität mit einem anderen Etikett.",
|
||||||
"silent_payments_display_card": "Zeigen Sie stille Zahlungskarte",
|
"silent_payments_display_card": "Zeigen Sie stille Zahlungskarte",
|
||||||
|
"silent_payments_register_key": "Registrieren Sie die Ansichtsschlüssel für schnelleres Scannen",
|
||||||
"silent_payments_scan_from_date": "Scan ab Datum",
|
"silent_payments_scan_from_date": "Scan ab Datum",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Wallet jeden Block scannt oder nur die angegebene Höhe überprüft.",
|
"silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Wallet jeden Block scannt oder nur die angegebene Höhe überprüft.",
|
||||||
"silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen",
|
"silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen",
|
||||||
|
|
|
@ -712,6 +712,7 @@
|
||||||
"silent_payments_always_scan": "Set Silent Payments always scanning",
|
"silent_payments_always_scan": "Set Silent Payments always scanning",
|
||||||
"silent_payments_disclaimer": "New addresses are not new identities. It is a re-use of an existing identity with a different label.",
|
"silent_payments_disclaimer": "New addresses are not new identities. It is a re-use of an existing identity with a different label.",
|
||||||
"silent_payments_display_card": "Show Silent Payments card",
|
"silent_payments_display_card": "Show Silent Payments card",
|
||||||
|
"silent_payments_register_key": "Register view key for faster scanning",
|
||||||
"silent_payments_scan_from_date": "Scan from date",
|
"silent_payments_scan_from_date": "Scan from date",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Please enter the block height you want to start scanning for incoming Silent Payments or use the date instead. You can choose if the wallet continues scanning every block, or checks only the specified height.",
|
"silent_payments_scan_from_date_or_blockheight": "Please enter the block height you want to start scanning for incoming Silent Payments or use the date instead. You can choose if the wallet continues scanning every block, or checks only the specified height.",
|
||||||
"silent_payments_scan_from_height": "Scan from block height",
|
"silent_payments_scan_from_height": "Scan from block height",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"about_cake_pay": "Cake Pay le permite comprar fácilmente tarjetas de regalo con activos virtuales, gastables instantáneamente en más de 150 000 comerciantes en los Estados Unidos.",
|
"about_cake_pay": "Cake Pay te permite comprar fácilmente tarjetas de regalo con activos virtuales, gastables instantáneamente en más de 150,000 comerciantes en los Estados Unidos.",
|
||||||
"account": "Cuenta",
|
"account": "Cuenta",
|
||||||
"accounts": "Cuentas",
|
"accounts": "Cuentas",
|
||||||
"accounts_subaddresses": "Cuentas y subdirecciones",
|
"accounts_subaddresses": "Cuentas y subdirecciones",
|
||||||
|
@ -21,13 +21,13 @@
|
||||||
"add_token_disclaimer_check": "He confirmado la dirección del contrato del token y la información utilizando una fuente confiable. Agregar información maliciosa o incorrecta puede resultar en una pérdida de fondos.",
|
"add_token_disclaimer_check": "He confirmado la dirección del contrato del token y la información utilizando una fuente confiable. Agregar información maliciosa o incorrecta puede resultar en una pérdida de fondos.",
|
||||||
"add_token_warning": "No edite ni agregue tokens según las instrucciones de los estafadores.\n¡Confirme siempre las direcciones de los tokens con fuentes acreditadas!",
|
"add_token_warning": "No edite ni agregue tokens según las instrucciones de los estafadores.\n¡Confirme siempre las direcciones de los tokens con fuentes acreditadas!",
|
||||||
"add_value": "Añadir valor",
|
"add_value": "Añadir valor",
|
||||||
"address": "DIRECCIÓN",
|
"address": "Dirección",
|
||||||
"address_book": "Libreta de direcciones",
|
"address_book": "Libreta de direcciones",
|
||||||
"address_book_menu": "Libreta de direcciones",
|
"address_book_menu": "Libreta de direcciones",
|
||||||
"address_detected": "Dirección detectada",
|
"address_detected": "Dirección detectada",
|
||||||
"address_from_domain": "Esta dirección es de ${domain} en Unstoppable Domains",
|
"address_from_domain": "Esta dirección es de ${domain} en Unstoppable Domains",
|
||||||
"address_from_yat": "Esta dirección es de ${emoji} en Yat",
|
"address_from_yat": "Esta dirección es de ${emoji} en Yat",
|
||||||
"address_label": "Address label",
|
"address_label": "Etiqueta de dirección",
|
||||||
"address_remove_contact": "Remover contacto",
|
"address_remove_contact": "Remover contacto",
|
||||||
"address_remove_content": "¿Estás seguro de que quieres eliminar el contacto seleccionado?",
|
"address_remove_content": "¿Estás seguro de que quieres eliminar el contacto seleccionado?",
|
||||||
"addresses": "Direcciones",
|
"addresses": "Direcciones",
|
||||||
|
@ -37,12 +37,12 @@
|
||||||
"agree_and_continue": "Aceptar y continuar",
|
"agree_and_continue": "Aceptar y continuar",
|
||||||
"agree_to": "Al crear una cuenta, aceptas ",
|
"agree_to": "Al crear una cuenta, aceptas ",
|
||||||
"alert_notice": "Aviso",
|
"alert_notice": "Aviso",
|
||||||
"all": "TODOS",
|
"all": "Todos",
|
||||||
"all_trades": "Todos los oficios",
|
"all_trades": "Todos los oficios",
|
||||||
"all_transactions": "Todas las transacciones",
|
"all_transactions": "Todas las transacciones",
|
||||||
"alphabetical": "Alfabético",
|
"alphabetical": "Alfabético",
|
||||||
"already_have_account": "¿Ya tienes una cuenta?",
|
"already_have_account": "¿Ya tienes una cuenta?",
|
||||||
"always": "siempre",
|
"always": "Siempre",
|
||||||
"amount": "Cantidad: ",
|
"amount": "Cantidad: ",
|
||||||
"amount_is_below_minimum_limit": "Su saldo después de las tarifas sería menor que la cantidad mínima necesaria para el intercambio (${min})",
|
"amount_is_below_minimum_limit": "Su saldo después de las tarifas sería menor que la cantidad mínima necesaria para el intercambio (${min})",
|
||||||
"amount_is_estimate": "El monto recibido es un estimado",
|
"amount_is_estimate": "El monto recibido es un estimado",
|
||||||
|
@ -54,17 +54,17 @@
|
||||||
"arrive_in_this_address": "${currency} ${tag}llegará a esta dirección",
|
"arrive_in_this_address": "${currency} ${tag}llegará a esta dirección",
|
||||||
"ascending": "Ascendente",
|
"ascending": "Ascendente",
|
||||||
"ask_each_time": "Pregunta cada vez",
|
"ask_each_time": "Pregunta cada vez",
|
||||||
"auth_store_ban_timeout": "prohibición de tiempo de espera",
|
"auth_store_ban_timeout": "Prohibición de tiempo de espera",
|
||||||
"auth_store_banned_for": "Prohibido para ",
|
"auth_store_banned_for": "Prohibido para ",
|
||||||
"auth_store_banned_minutes": " minutos",
|
"auth_store_banned_minutes": " minutos",
|
||||||
"auth_store_incorrect_password": "Contraseña PIN",
|
"auth_store_incorrect_password": "Contraseña PIN",
|
||||||
"authenticated": "Autenticados",
|
"authenticated": "Autenticados",
|
||||||
"authentication": "Autenticación",
|
"authentication": "Autenticación",
|
||||||
"auto_generate_addresses": "Auto Generar direcciones",
|
"auto_generate_addresses": "Auto-generar nuevas direcciones",
|
||||||
"auto_generate_subaddresses": "Generar subdirecciones automáticamente",
|
"auto_generate_subaddresses": "Generar subdirecciones automáticamente",
|
||||||
"automatic": "Automático",
|
"automatic": "Automático",
|
||||||
"available_balance": "Balance disponible",
|
"available_balance": "Balance disponible",
|
||||||
"available_balance_description": "Su saldo disponible es la cantidad de fondos que puede gastar. Los fondos que se muestran aquí se pueden gastar inmediatamente.",
|
"available_balance_description": "Tu saldo disponible es la cantidad de fondos que puedes gastar. Los fondos que se muestran aquí, se pueden gastar inmediatamente.",
|
||||||
"avg_savings": "Ahorro promedio",
|
"avg_savings": "Ahorro promedio",
|
||||||
"awaitDAppProcessing": "Espere a que la dApp termine de procesarse.",
|
"awaitDAppProcessing": "Espere a que la dApp termine de procesarse.",
|
||||||
"awaiting_payment_confirmation": "Esperando confirmación de pago",
|
"awaiting_payment_confirmation": "Esperando confirmación de pago",
|
||||||
|
@ -78,8 +78,8 @@
|
||||||
"billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío",
|
"billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío",
|
||||||
"biometric_auth_reason": "Escanee su huella digital para autenticar",
|
"biometric_auth_reason": "Escanee su huella digital para autenticar",
|
||||||
"bitcoin_dark_theme": "Tema oscuro de Bitcoin",
|
"bitcoin_dark_theme": "Tema oscuro de Bitcoin",
|
||||||
"bitcoin_light_theme": "Tema de la luz de Bitcoin",
|
"bitcoin_light_theme": "Tema claro de Bitcoin",
|
||||||
"bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.",
|
"bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por tu paciencia! Se te enviará un correo electrónico cuando se confirme el pago.",
|
||||||
"block_remaining": "1 bloqueo restante",
|
"block_remaining": "1 bloqueo restante",
|
||||||
"Blocks_remaining": "${status} Bloques restantes",
|
"Blocks_remaining": "${status} Bloques restantes",
|
||||||
"bluetooth": "Bluetooth",
|
"bluetooth": "Bluetooth",
|
||||||
|
@ -93,26 +93,26 @@
|
||||||
"buy_with": "Compra con",
|
"buy_with": "Compra con",
|
||||||
"by_cake_pay": "por Cake Pay",
|
"by_cake_pay": "por Cake Pay",
|
||||||
"cake_2fa_preset": "Pastel 2FA preestablecido",
|
"cake_2fa_preset": "Pastel 2FA preestablecido",
|
||||||
"cake_dark_theme": "Tema oscuro del pastel",
|
"cake_dark_theme": "Tema oscuro",
|
||||||
"cake_pay_account_note": "Regístrese con solo una dirección de correo electrónico para ver y comprar tarjetas. ¡Algunas incluso están disponibles con descuento!",
|
"cake_pay_account_note": "Regístrate con solo una dirección de correo electrónico para ver y comprar tarjetas. ¡Algunas incluso están disponibles con descuento!",
|
||||||
"cake_pay_learn_more": "¡Compre y canjee tarjetas de regalo al instante en la aplicación!\nDeslice el dedo de izquierda a derecha para obtener más información.",
|
"cake_pay_learn_more": "¡Compra y canjea tarjetas de regalo al instante en la aplicación!\nDesliza el dedo de izquierda a derecha para obtener más información.",
|
||||||
"cake_pay_save_order": "La tarjeta debe enviarse a su correo electrónico dentro de 1 día hábil \n Guardar su ID de pedido:",
|
"cake_pay_save_order": "La tarjeta debe enviarse a tu correo electrónico dentro de 1 día hábil \n Guardar su ID de pedido:",
|
||||||
"cake_pay_subtitle": "Compre tarjetas prepagas y tarjetas de regalo en todo el mundo",
|
"cake_pay_subtitle": "Compra tarjetas prepagadas y tarjetas de regalo en todo el mundo",
|
||||||
"cake_pay_web_cards_subtitle": "Compre tarjetas de prepago y tarjetas de regalo en todo el mundo",
|
"cake_pay_web_cards_subtitle": "Compra tarjetas de prepago y tarjetas de regalo en todo el mundo",
|
||||||
"cake_pay_web_cards_title": "Tarjetas Web Cake Pay",
|
"cake_pay_web_cards_title": "Tarjetas Web Cake Pay",
|
||||||
"cake_wallet": "Cake Wallet",
|
"cake_wallet": "Cake Wallet",
|
||||||
"cakepay_prepaid_card": "Tarjeta de Débito Prepago CakePay",
|
"cakepay_prepaid_card": "Tarjeta de Débito Prepago CakePay",
|
||||||
"camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulte su Política de privacidad para obtener más detalles.",
|
"camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulta tu Política de privacidad para obtener más detalles.",
|
||||||
"camera_permission_is_required": "Se requiere permiso de la cámara.\nHabilítelo desde la configuración de la aplicación.",
|
"camera_permission_is_required": "Se requiere permiso de la cámara.\nHabilítalo desde la configuración de la aplicación.",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"card_address": "Dirección:",
|
"card_address": "Dirección:",
|
||||||
"cardholder_agreement": "Acuerdo del titular de la tarjeta",
|
"cardholder_agreement": "Acuerdo del titular de la tarjeta",
|
||||||
"cards": "Cartas",
|
"cards": "Cartas",
|
||||||
"chains": "Cadenas",
|
"chains": "Cadenas",
|
||||||
"change": "Cambio",
|
"change": "Cambio",
|
||||||
"change_backup_password_alert": "Sus archivos de respaldo anteriores no estarán disponibles para importar con la nueva contraseña de respaldo. La nueva contraseña de respaldo se utilizará solo para los nuevos archivos de respaldo. ¿Está seguro de que desea cambiar la contraseña de respaldo?",
|
"change_backup_password_alert": "Tus archivos de respaldo anteriores no estarán disponibles para importar con la nueva contraseña de respaldo. La nueva contraseña de respaldo se utilizará solo para los nuevos archivos de respaldo. ¿Está seguro de que desea cambiar la contraseña de respaldo?",
|
||||||
"change_currency": "Cambiar moneda",
|
"change_currency": "Cambiar moneda",
|
||||||
"change_current_node": "¿Está seguro de cambiar el nodo actual a ${node}?",
|
"change_current_node": "¿Estás seguro de cambiar el nodo actual a ${node}?",
|
||||||
"change_current_node_title": "Cambiar el nodo actual",
|
"change_current_node_title": "Cambiar el nodo actual",
|
||||||
"change_exchange_provider": "Cambiar proveedor de intercambio",
|
"change_exchange_provider": "Cambiar proveedor de intercambio",
|
||||||
"change_language": "Cambiar idioma",
|
"change_language": "Cambiar idioma",
|
||||||
|
@ -125,38 +125,38 @@
|
||||||
"change_wallet_alert_title": "Cambiar billetera actual",
|
"change_wallet_alert_title": "Cambiar billetera actual",
|
||||||
"choose_account": "Elegir cuenta",
|
"choose_account": "Elegir cuenta",
|
||||||
"choose_address": "\n\nPor favor elija la dirección:",
|
"choose_address": "\n\nPor favor elija la dirección:",
|
||||||
"choose_card_value": "Elija un valor de tarjeta",
|
"choose_card_value": "Elige un valor de tarjeta",
|
||||||
"choose_derivation": "Elija la derivación de la billetera",
|
"choose_derivation": "Elige la derivación de la billetera",
|
||||||
"choose_from_available_options": "Elija entre las opciones disponibles:",
|
"choose_from_available_options": "Elige entre las opciones disponibles:",
|
||||||
"choose_one": "Elige uno",
|
"choose_one": "Elige uno",
|
||||||
"choose_relay": "Por favor elija un relé para usar",
|
"choose_relay": "Por favor elige un relay para usar",
|
||||||
"choose_wallet_currency": "Por favor, elija la moneda de la billetera:",
|
"choose_wallet_currency": "Por favor, elige la moneda de la billetera:",
|
||||||
"choose_wallet_group": "Elija el grupo de billetera",
|
"choose_wallet_group": "Elige el grupo de billetera",
|
||||||
"clear": "Claro",
|
"clear": "Claro",
|
||||||
"clearnet_link": "enlace Clearnet",
|
"clearnet_link": "enlace Clearnet",
|
||||||
"close": "Cerca",
|
"close": "Cerca",
|
||||||
"coin_control": "Control de monedas (opcional)",
|
"coin_control": "Control de monedas (opcional)",
|
||||||
"cold_or_recover_wallet": "Agregue una billetera fría o recupere una billetera de papel",
|
"cold_or_recover_wallet": "Agrega una billetera fría o recupera una billetera de papel",
|
||||||
"color_theme": "Tema de color",
|
"color_theme": "Tema de color",
|
||||||
"commit_transaction_amount_fee": "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}",
|
"commit_transaction_amount_fee": "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}",
|
||||||
"confirm": "Confirmar",
|
"confirm": "Confirmar",
|
||||||
"confirm_delete_template": "Esta acción eliminará esta plantilla. ¿Desea continuar?",
|
"confirm_delete_template": "Esta acción eliminará esta plantilla. ¿Deseas continuar?",
|
||||||
"confirm_delete_wallet": "Esta acción eliminará esta billetera. ¿Desea continuar?",
|
"confirm_delete_wallet": "Esta acción eliminará esta billetera. ¿Deseas continuar?",
|
||||||
"confirm_fee_deduction": "Confirmar la deducción de la tarifa",
|
"confirm_fee_deduction": "Confirmar la deducción de la tarifa",
|
||||||
"confirm_fee_deduction_content": "¿Acepta deducir la tarifa de la producción?",
|
"confirm_fee_deduction_content": "¿Aceptas deducir la tarifa de la producción?",
|
||||||
"confirm_passphrase": "Confirmar la frase de pases",
|
"confirm_passphrase": "Confirmar la contraseña",
|
||||||
"confirm_sending": "Confirmar envío",
|
"confirm_sending": "Confirmar envío",
|
||||||
"confirm_silent_payments_switch_node": "Su nodo actual no admite pagos silenciosos \\ ncake billet cambiará a un nodo compatible, solo para escanear",
|
"confirm_silent_payments_switch_node": "Tu nodo actual no admite pagos silenciosos \\ nCake cambiará a un nodo compatible, solo para escanear",
|
||||||
"confirmations": "Confirmaciones",
|
"confirmations": "Confirmaciones",
|
||||||
"confirmed": "Saldo confirmado",
|
"confirmed": "Saldo confirmado",
|
||||||
"confirmed_tx": "Confirmado",
|
"confirmed_tx": "Confirmado",
|
||||||
"congratulations": "Felicidades!",
|
"congratulations": "Felicidades!",
|
||||||
"connect_an_existing_yat": "Conectar un Yat existente",
|
"connect_an_existing_yat": "Conectar un Yat existente",
|
||||||
"connect_yats": "Conectar Yats",
|
"connect_yats": "Conectar Yats",
|
||||||
"connect_your_hardware_wallet": "Conecte su billetera de hardware con Bluetooth o USB",
|
"connect_your_hardware_wallet": "Conecta tu billetera de hardware con Bluetooth o USB",
|
||||||
"connect_your_hardware_wallet_ios": "Conecte su billetera de hardware con Bluetooth",
|
"connect_your_hardware_wallet_ios": "Conecta tu billetera de hardware con Bluetooth",
|
||||||
"connection_sync": "Conexión y sincronización",
|
"connection_sync": "Conexión y sincronización",
|
||||||
"connectWalletPrompt": "Conecte su billetera con WalletConnect para realizar transacciones",
|
"connectWalletPrompt": "Conecte tu billetera con WalletConnect para realizar transacciones",
|
||||||
"contact": "Contacto",
|
"contact": "Contacto",
|
||||||
"contact_list_contacts": "Contactos",
|
"contact_list_contacts": "Contactos",
|
||||||
"contact_list_wallets": "Mis billeteras",
|
"contact_list_wallets": "Mis billeteras",
|
||||||
|
@ -187,7 +187,7 @@
|
||||||
"custom_drag": "Custom (mantenía y arrastre)",
|
"custom_drag": "Custom (mantenía y arrastre)",
|
||||||
"custom_redeem_amount": "Cantidad de canje personalizada",
|
"custom_redeem_amount": "Cantidad de canje personalizada",
|
||||||
"custom_value": "Valor personalizado",
|
"custom_value": "Valor personalizado",
|
||||||
"dark_theme": "Oscura",
|
"dark_theme": "Oscuro",
|
||||||
"debit_card": "Tarjeta de Débito",
|
"debit_card": "Tarjeta de Débito",
|
||||||
"debit_card_terms": "El almacenamiento y el uso de su número de tarjeta de pago (y las credenciales correspondientes a su número de tarjeta de pago) en esta billetera digital están sujetos a los Términos y condiciones del acuerdo del titular de la tarjeta aplicable con el emisor de la tarjeta de pago, en vigor desde tiempo al tiempo.",
|
"debit_card_terms": "El almacenamiento y el uso de su número de tarjeta de pago (y las credenciales correspondientes a su número de tarjeta de pago) en esta billetera digital están sujetos a los Términos y condiciones del acuerdo del titular de la tarjeta aplicable con el emisor de la tarjeta de pago, en vigor desde tiempo al tiempo.",
|
||||||
"decimal_places_error": "Demasiados lugares decimales",
|
"decimal_places_error": "Demasiados lugares decimales",
|
||||||
|
@ -197,21 +197,21 @@
|
||||||
"delete": "Borrar",
|
"delete": "Borrar",
|
||||||
"delete_account": "Eliminar cuenta",
|
"delete_account": "Eliminar cuenta",
|
||||||
"delete_wallet": "Eliminar billetera",
|
"delete_wallet": "Eliminar billetera",
|
||||||
"delete_wallet_confirm_message": "¿Está seguro de que desea eliminar la billetera ${wallet_name}?",
|
"delete_wallet_confirm_message": "¿Estás seguro de que deseas eliminar la billetera ${wallet_name}?",
|
||||||
"deleteConnectionConfirmationPrompt": "¿Está seguro de que desea eliminar la conexión a",
|
"deleteConnectionConfirmationPrompt": "¿Estás seguro de que deseas eliminar la conexión a",
|
||||||
"denominations": "Denominaciones",
|
"denominations": "Denominaciones",
|
||||||
"derivationpath": "Ruta de derivación",
|
"derivationpath": "Ruta de derivación",
|
||||||
"descending": "Descendente",
|
"descending": "Descendente",
|
||||||
"description": "Descripción",
|
"description": "Descripción",
|
||||||
"destination_tag": "Etiqueta de destino:",
|
"destination_tag": "Etiqueta de destino:",
|
||||||
"dfx_option_description": "Compre criptografía con EUR y CHF. Para clientes minoristas y corporativos en Europa",
|
"dfx_option_description": "Compre cripto con EUR y CHF. Para clientes minoristas y corporativos en Europa",
|
||||||
"didnt_get_code": "¿No recibiste el código?",
|
"didnt_get_code": "¿No recibiste el código?",
|
||||||
"digit_pin": "-dígito PIN",
|
"digit_pin": "-dígito PIN",
|
||||||
"digital_and_physical_card": " tarjeta de débito prepago digital y física",
|
"digital_and_physical_card": " tarjeta de débito prepago digital y física",
|
||||||
"disable": "Desactivar",
|
"disable": "Desactivar",
|
||||||
"disable_bulletin": "Desactivar el boletín de estado del servicio",
|
"disable_bulletin": "Desactivar el boletín de estado del servicio",
|
||||||
"disable_buy": "Desactivar acción de compra",
|
"disable_buy": "Desactivar acción de compra",
|
||||||
"disable_cake_2fa": "Desactivar pastel 2FA",
|
"disable_cake_2fa": "Desactivar 2FA",
|
||||||
"disable_exchange": "Deshabilitar intercambio",
|
"disable_exchange": "Deshabilitar intercambio",
|
||||||
"disable_fee_api_warning": "Al apagar esto, las tasas de tarifas pueden ser inexactas en algunos casos, por lo que puede terminar pagando en exceso o pagando menos las tarifas por sus transacciones",
|
"disable_fee_api_warning": "Al apagar esto, las tasas de tarifas pueden ser inexactas en algunos casos, por lo que puede terminar pagando en exceso o pagando menos las tarifas por sus transacciones",
|
||||||
"disable_fiat": "Deshabilitar fiat",
|
"disable_fiat": "Deshabilitar fiat",
|
||||||
|
@ -224,7 +224,7 @@
|
||||||
"displayable": "Visualizable",
|
"displayable": "Visualizable",
|
||||||
"do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.",
|
"do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.",
|
||||||
"do_not_send": "no enviar",
|
"do_not_send": "no enviar",
|
||||||
"do_not_share_warning_text": "No comparta estos con nadie más, incluido el soporte.\n\n¡Sus fondos pueden ser y serán robados!",
|
"do_not_share_warning_text": "No compartas estos con nadie más, incluido el soporte.\n\n¡Tus fondos pueden ser y serán robados!",
|
||||||
"do_not_show_me": "no me muestres esto otra vez",
|
"do_not_show_me": "no me muestres esto otra vez",
|
||||||
"domain_looks_up": "Búsquedas de dominio",
|
"domain_looks_up": "Búsquedas de dominio",
|
||||||
"donation_link_details": "Detalles del enlace de donación",
|
"donation_link_details": "Detalles del enlace de donación",
|
||||||
|
@ -238,21 +238,21 @@
|
||||||
"enable": "Permitir",
|
"enable": "Permitir",
|
||||||
"enable_mempool_api": "API de Mempool para tarifas y fechas precisas",
|
"enable_mempool_api": "API de Mempool para tarifas y fechas precisas",
|
||||||
"enable_replace_by_fee": "Habilitar reemplazar por tarea",
|
"enable_replace_by_fee": "Habilitar reemplazar por tarea",
|
||||||
"enable_silent_payments_scanning": "Comience a escanear pagos silenciosos, hasta que se alcance la punta",
|
"enable_silent_payments_scanning": "Comienza a escanear pagos silenciosos, hasta que se alcance la altura actual",
|
||||||
"enabled": "Activado",
|
"enabled": "Activado",
|
||||||
"enter_amount": "Ingrese la cantidad",
|
"enter_amount": "Ingresa la cantidad",
|
||||||
"enter_backup_password": "Ingrese la contraseña de respaldo aquí",
|
"enter_backup_password": "Ingresa la contraseña de respaldo aquí",
|
||||||
"enter_code": "Ingresar código",
|
"enter_code": "Ingresar código",
|
||||||
"enter_seed_phrase": "Ingrese su frase de semillas",
|
"enter_seed_phrase": "Ingresa su frase de semillas",
|
||||||
"enter_totp_code": "Ingrese el código TOTP.",
|
"enter_totp_code": "Ingresa el código TOTP.",
|
||||||
"enter_wallet_password": "Ingrese la contraseña de la billetera",
|
"enter_wallet_password": "Ingresa la contraseña de la billetera",
|
||||||
"enter_your_note": "Ingresa tu nota…",
|
"enter_your_note": "Ingresa tu nota…",
|
||||||
"enter_your_pin": "Introduce tu PIN",
|
"enter_your_pin": "Introduce tu PIN",
|
||||||
"enter_your_pin_again": "Ingrese su PIN nuevamente",
|
"enter_your_pin_again": "Ingresa su PIN nuevamente",
|
||||||
"enterTokenID": "Ingrese el ID del token",
|
"enterTokenID": "Ingresa el ID del token",
|
||||||
"enterWalletConnectURI": "Ingrese el URI de WalletConnect",
|
"enterWalletConnectURI": "Ingresa el URI de WalletConnect",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error_dialog_content": "Vaya, tenemos un error.\n\nEnvíe el informe de bloqueo a nuestro equipo de soporte para mejorar la aplicación.",
|
"error_dialog_content": "Vaya, tenemos un error.\n\nEnvía el informe de error a nuestro equipo de soporte para mejorar la aplicación.",
|
||||||
"error_text_account_name": "El nombre de la cuenta solo puede contener letras, números \ny debe tener entre 1 y 15 caracteres de longitud",
|
"error_text_account_name": "El nombre de la cuenta solo puede contener letras, números \ny debe tener entre 1 y 15 caracteres de longitud",
|
||||||
"error_text_address": "La dirección de la billetera debe corresponder al tipo \nde criptomoneda",
|
"error_text_address": "La dirección de la billetera debe corresponder al tipo \nde criptomoneda",
|
||||||
"error_text_amount": "La cantidad solo puede contener números",
|
"error_text_amount": "La cantidad solo puede contener números",
|
||||||
|
@ -281,7 +281,7 @@
|
||||||
"etherscan_history": "historia de etherscan",
|
"etherscan_history": "historia de etherscan",
|
||||||
"event": "Evento",
|
"event": "Evento",
|
||||||
"events": "Eventos",
|
"events": "Eventos",
|
||||||
"exchange": "Intercambio",
|
"exchange": "Intercambiar",
|
||||||
"exchange_incorrect_current_wallet_for_xmr": "Si desea intercambiar XMR desde su billetera de pastel Monero Balance, primero cambie a su billetera Monero.",
|
"exchange_incorrect_current_wallet_for_xmr": "Si desea intercambiar XMR desde su billetera de pastel Monero Balance, primero cambie a su billetera Monero.",
|
||||||
"exchange_new_template": "Nueva plantilla",
|
"exchange_new_template": "Nueva plantilla",
|
||||||
"exchange_provider_unsupported": "¡${providerName} ya no es compatible!",
|
"exchange_provider_unsupported": "¡${providerName} ya no es compatible!",
|
||||||
|
@ -304,14 +304,14 @@
|
||||||
"fee_rate": "Tarifa",
|
"fee_rate": "Tarifa",
|
||||||
"fetching": "Cargando",
|
"fetching": "Cargando",
|
||||||
"fiat_api": "Fiat API",
|
"fiat_api": "Fiat API",
|
||||||
"fiat_balance": "Equilibrio Fiat",
|
"fiat_balance": "Balance fiat",
|
||||||
"field_required": "Este campo es obligatorio",
|
"field_required": "Este campo es obligatorio",
|
||||||
"fill_code": "Por favor complete el código de verificación proporcionado a su correo electrónico",
|
"fill_code": "Por favor completa el código de verificación proporcionado en tu correo electrónico",
|
||||||
"filter_by": "Filtrado por",
|
"filter_by": "Filtrado por",
|
||||||
"first_wallet_text": "Impresionante billetera para Monero, Bitcoin, Ethereum, Litecoin, y Haven",
|
"first_wallet_text": "Impresionante billetera para Monero, Bitcoin, Ethereum, Litecoin, y Haven",
|
||||||
"fixed_pair_not_supported": "Este par fijo no es compatible con los servicios de intercambio seleccionados",
|
"fixed_pair_not_supported": "Este par fijo no es compatible con los servicios de intercambio seleccionados",
|
||||||
"fixed_rate": "Tipo de interés fijo",
|
"fixed_rate": "Tipo de interés fijo",
|
||||||
"fixed_rate_alert": "Podrá ingresar la cantidad recibida cuando el modo de tarifa fija esté marcado. ¿Quieres cambiar al modo de tarifa fija?",
|
"fixed_rate_alert": "Podrás ingresar la cantidad recibida cuando el modo de tarifa fija esté marcado. ¿Quieres cambiar al modo de tarifa fija?",
|
||||||
"forgot_password": "Olvidé mi contraseña",
|
"forgot_password": "Olvidé mi contraseña",
|
||||||
"freeze": "Congelar",
|
"freeze": "Congelar",
|
||||||
"frequently_asked_questions": "Preguntas frecuentes",
|
"frequently_asked_questions": "Preguntas frecuentes",
|
||||||
|
@ -333,7 +333,7 @@
|
||||||
"gross_balance": "Saldo bruto",
|
"gross_balance": "Saldo bruto",
|
||||||
"group_by_type": "Grupo por tipo",
|
"group_by_type": "Grupo por tipo",
|
||||||
"haven_app": "Haven by Cake Wallet",
|
"haven_app": "Haven by Cake Wallet",
|
||||||
"haven_app_wallet_text": "Awesome wallet for Haven",
|
"haven_app_wallet_text": "Increíble billetera para Haven",
|
||||||
"help": "ayuda",
|
"help": "ayuda",
|
||||||
"hidden_balance": "Balance oculto",
|
"hidden_balance": "Balance oculto",
|
||||||
"hide_details": "Ocultar detalles",
|
"hide_details": "Ocultar detalles",
|
||||||
|
@ -349,9 +349,9 @@
|
||||||
"incoming": "Entrante",
|
"incoming": "Entrante",
|
||||||
"incorrect_seed": "El texto ingresado no es válido.",
|
"incorrect_seed": "El texto ingresado no es válido.",
|
||||||
"inputs": "Entradas",
|
"inputs": "Entradas",
|
||||||
"insufficient_lamport_for_tx": "No tiene suficiente SOL para cubrir la transacción y su tarifa de transacción. Por favor, agregue más SOL a su billetera o reduzca la cantidad de sol que está enviando.",
|
"insufficient_lamport_for_tx": "No tienes suficiente SOL para cubrir la transacción y su tarifa de transacción. Por favor, agrega más SOL a su billetera o reduce la cantidad de sol que está enviando.",
|
||||||
"insufficient_lamports": "No tiene suficiente SOL para cubrir la transacción y su tarifa de transacción. Necesita al menos ${solValueNeeded} sol. Por favor, agregue más sol a su billetera o reduzca la cantidad de sol que está enviando",
|
"insufficient_lamports": "No tienes suficiente SOL para cubrir la transacción y su tarifa de transacción. Necesita al menos ${solValueNeeded} sol. Por favor, agrega más sol a su billetera o reduzca la cantidad de sol que está enviando",
|
||||||
"insufficientFundsForRentError": "No tiene suficiente SOL para cubrir la tarifa de transacción y alquilar para la cuenta. Por favor, agregue más sol a su billetera o reduzca la cantidad de sol que está enviando",
|
"insufficientFundsForRentError": "No tienes suficiente SOL para cubrir la tarifa de transacción y alquilar para la cuenta. Por favor, agrega más sol a su billetera o reduce la cantidad de sol que está enviando",
|
||||||
"introducing_cake_pay": "¡Presentamos Cake Pay!",
|
"introducing_cake_pay": "¡Presentamos Cake Pay!",
|
||||||
"invalid_input": "Entrada inválida",
|
"invalid_input": "Entrada inválida",
|
||||||
"invalid_password": "Contraseña invalida",
|
"invalid_password": "Contraseña invalida",
|
||||||
|
@ -359,12 +359,12 @@
|
||||||
"is_percentage": "es",
|
"is_percentage": "es",
|
||||||
"last_30_days": "Últimos 30 días",
|
"last_30_days": "Últimos 30 días",
|
||||||
"learn_more": "Aprende más",
|
"learn_more": "Aprende más",
|
||||||
"ledger_connection_error": "No se pudo conectar con su libro mayor. Inténtalo de nuevo.",
|
"ledger_connection_error": "No se pudo conectar con ledger. Inténtalo de nuevo.",
|
||||||
"ledger_error_device_locked": "El libro mayor está bloqueado",
|
"ledger_error_device_locked": "Ledger está bloqueado",
|
||||||
"ledger_error_tx_rejected_by_user": "Transacción rechazada en el dispositivo",
|
"ledger_error_tx_rejected_by_user": "Transacción rechazada en el dispositivo",
|
||||||
"ledger_error_wrong_app": "Por favor, asegúrese de abrir la aplicación correcta en su libro mayor.",
|
"ledger_error_wrong_app": "Por favor, asegúrate de abrir la aplicación correcta en su libro mayor.",
|
||||||
"ledger_please_enable_bluetooth": "Habilite Bluetooth para detectar su libro mayor",
|
"ledger_please_enable_bluetooth": "Habilita tu Bluetooth para detectar tu ledger",
|
||||||
"light_theme": "Ligera",
|
"light_theme": "Ligero",
|
||||||
"litecoin_enable_mweb_sync": "Habilitar el escaneo mweb",
|
"litecoin_enable_mweb_sync": "Habilitar el escaneo mweb",
|
||||||
"litecoin_mweb": "Mweb",
|
"litecoin_mweb": "Mweb",
|
||||||
"litecoin_mweb_always_scan": "Establecer mweb siempre escaneo",
|
"litecoin_mweb_always_scan": "Establecer mweb siempre escaneo",
|
||||||
|
@ -372,8 +372,8 @@
|
||||||
"litecoin_mweb_dismiss": "Despedir",
|
"litecoin_mweb_dismiss": "Despedir",
|
||||||
"litecoin_mweb_display_card": "Mostrar tarjeta MWEB",
|
"litecoin_mweb_display_card": "Mostrar tarjeta MWEB",
|
||||||
"litecoin_mweb_enable_later": "Puede elegir habilitar MWEB nuevamente en la configuración de visualización.",
|
"litecoin_mweb_enable_later": "Puede elegir habilitar MWEB nuevamente en la configuración de visualización.",
|
||||||
"litecoin_mweb_pegin": "Meter",
|
"litecoin_mweb_pegin": "Convertir",
|
||||||
"litecoin_mweb_pegout": "Estirar la pata",
|
"litecoin_mweb_pegout": "Recuperar",
|
||||||
"litecoin_mweb_scanning": "Escaneo mweb",
|
"litecoin_mweb_scanning": "Escaneo mweb",
|
||||||
"litecoin_mweb_settings": "Configuración de MWEB",
|
"litecoin_mweb_settings": "Configuración de MWEB",
|
||||||
"litecoin_mweb_warning": "El uso de MWEB inicialmente descargará ~ 600 MB de datos, y puede tomar hasta 30 minutos según la velocidad de la red. Estos datos iniciales solo se descargarán una vez y estarán disponibles para todas las billeteras de Litecoin",
|
"litecoin_mweb_warning": "El uso de MWEB inicialmente descargará ~ 600 MB de datos, y puede tomar hasta 30 minutos según la velocidad de la red. Estos datos iniciales solo se descargarán una vez y estarán disponibles para todas las billeteras de Litecoin",
|
||||||
|
@ -401,9 +401,9 @@
|
||||||
"min_value": "Min: ${value} ${currency}",
|
"min_value": "Min: ${value} ${currency}",
|
||||||
"minutes_to_pin_code": "${minute} minutos",
|
"minutes_to_pin_code": "${minute} minutos",
|
||||||
"mm": "mm",
|
"mm": "mm",
|
||||||
"modify_2fa": "Modificar torta 2FA",
|
"modify_2fa": "Modificar 2FA",
|
||||||
"monero_com": "Monero.com by Cake Wallet",
|
"monero_com": "Monero.com por Cake Wallet",
|
||||||
"monero_com_wallet_text": "Awesome wallet for Monero",
|
"monero_com_wallet_text": "Increíble billetera para Monero",
|
||||||
"monero_dark_theme": "Tema oscuro de Monero",
|
"monero_dark_theme": "Tema oscuro de Monero",
|
||||||
"monero_light_theme": "Tema ligero de Monero",
|
"monero_light_theme": "Tema ligero de Monero",
|
||||||
"moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}",
|
"moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}",
|
||||||
|
@ -412,12 +412,12 @@
|
||||||
"mweb_unconfirmed": "Mweb no confirmado",
|
"mweb_unconfirmed": "Mweb no confirmado",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
"nano_current_rep": "Representante actual",
|
"nano_current_rep": "Representante actual",
|
||||||
"nano_gpt_thanks_message": "¡Gracias por usar nanogpt! ¡Recuerde regresar al navegador después de que se complete su transacción!",
|
"nano_gpt_thanks_message": "¡Gracias por usar nanogpt! ¡Recuerda regresar al navegador después de que se complete su transacción!",
|
||||||
"nano_pick_new_rep": "Elija un nuevo representante",
|
"nano_pick_new_rep": "Elija un nuevo representante",
|
||||||
"nanogpt_subtitle": "Todos los modelos más nuevos (GPT-4, Claude). \\nSin suscripción, pague con cripto.",
|
"nanogpt_subtitle": "Todos los modelos más nuevos (GPT-4, Claude). \\nSin suscripción, pague con cripto.",
|
||||||
"narrow": "Angosto",
|
"narrow": "Angosto",
|
||||||
"new_first_wallet_text": "Mantenga fácilmente su criptomoneda segura",
|
"new_first_wallet_text": "Mantén fácilmente tu criptomoneda segura",
|
||||||
"new_node_testing": "Prueba de nuevos nodos",
|
"new_node_testing": "Prueba nuevos nodos",
|
||||||
"new_subaddress_create": "Crear",
|
"new_subaddress_create": "Crear",
|
||||||
"new_subaddress_label_name": "Nombre de etiqueta",
|
"new_subaddress_label_name": "Nombre de etiqueta",
|
||||||
"new_subaddress_title": "Nueva direccion",
|
"new_subaddress_title": "Nueva direccion",
|
||||||
|
@ -426,10 +426,10 @@
|
||||||
"newConnection": "Nueva conexión",
|
"newConnection": "Nueva conexión",
|
||||||
"no_cards_found": "No se encuentran cartas",
|
"no_cards_found": "No se encuentran cartas",
|
||||||
"no_id_needed": "¡No se necesita identificación!",
|
"no_id_needed": "¡No se necesita identificación!",
|
||||||
"no_id_required": "No se requiere identificación. Recargue y gaste en cualquier lugar",
|
"no_id_required": "No se requiere identificación. Recarga y gaste en cualquier lugar",
|
||||||
"no_relay_on_domain": "No hay una retransmisión para el dominio del usuario o la retransmisión no está disponible. Elija un relé para usar.",
|
"no_relay_on_domain": "No hay una retransmisión para el dominio del usuario o la retransmisión no está disponible. Elige un relay para usar.",
|
||||||
"no_relays": "Sin relevos",
|
"no_relays": "Sin relays",
|
||||||
"no_relays_message": "Encontramos un registro Nostr NIP-05 para este usuario, pero no contiene ningún relé. Indique al destinatario que agregue retransmisiones a su registro Nostr.",
|
"no_relays_message": "Encontramos un registro Nostr NIP-05 para este usuario, pero no contiene ningún relay. Indica al destinatario que agregue retransmisiones a su registro Nostr.",
|
||||||
"node_address": "Dirección de nodo",
|
"node_address": "Dirección de nodo",
|
||||||
"node_connection_failed": "La conexión falló",
|
"node_connection_failed": "La conexión falló",
|
||||||
"node_connection_successful": "La conexión fue exitosa",
|
"node_connection_successful": "La conexión fue exitosa",
|
||||||
|
@ -449,15 +449,15 @@
|
||||||
"offline": "fuera de línea",
|
"offline": "fuera de línea",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"old_fee": "Tarifa antigua",
|
"old_fee": "Tarifa antigua",
|
||||||
"onion_link": "Enlace de cebolla",
|
"onion_link": "Enlace de cebolla (Tor)",
|
||||||
"online": "En línea",
|
"online": "En línea",
|
||||||
"onramper_option_description": "Compre rápidamente cripto con muchos métodos de pago. Disponible en la mayoría de los países. Los diferenciales y las tarifas varían.",
|
"onramper_option_description": "Compra rápidamente cripto con muchos métodos de pago. Disponible en la mayoría de los países. Los diferenciales y las tarifas varían.",
|
||||||
"open_gift_card": "Abrir tarjeta de regalo",
|
"open_gift_card": "Abrir tarjeta de regalo",
|
||||||
"optional_description": "Descripción opcional",
|
"optional_description": "Descripción opcional",
|
||||||
"optional_email_hint": "Correo electrónico de notificación del beneficiario opcional",
|
"optional_email_hint": "Correo electrónico de notificación del beneficiario opcional",
|
||||||
"optional_name": "Nombre del destinatario opcional",
|
"optional_name": "Nombre del destinatario opcional",
|
||||||
"optionally_order_card": "Opcionalmente pide una tarjeta física.",
|
"optionally_order_card": "Opcionalmente pide una tarjeta física.",
|
||||||
"orbot_running_alert": "Asegúrese de que Orbot se esté ejecutando antes de conectarse a este nodo.",
|
"orbot_running_alert": "Asegúrate de que Orbot se esté ejecutando antes de conectarte a este nodo.",
|
||||||
"order_by": "Ordenar",
|
"order_by": "Ordenar",
|
||||||
"order_id": "Identificación del pedido",
|
"order_id": "Identificación del pedido",
|
||||||
"order_physical_card": "Pedir tarjeta física",
|
"order_physical_card": "Pedir tarjeta física",
|
||||||
|
@ -466,7 +466,7 @@
|
||||||
"outdated_electrum_wallet_receive_warning": "Si esta billetera tiene una semilla de 12 palabras y se creó en Cake, NO deposite Bitcoin en esta billetera. Cualquier BTC transferido a esta billetera se puede perder. Cree una nueva billetera de 24 palabras (toque el menú en la parte superior derecha, seleccione Monederos, elija Crear nueva billetera, luego seleccione Bitcoin) e INMEDIATAMENTE mueva su BTC allí. Las nuevas carteras BTC (24 palabras) de Cake son seguras",
|
"outdated_electrum_wallet_receive_warning": "Si esta billetera tiene una semilla de 12 palabras y se creó en Cake, NO deposite Bitcoin en esta billetera. Cualquier BTC transferido a esta billetera se puede perder. Cree una nueva billetera de 24 palabras (toque el menú en la parte superior derecha, seleccione Monederos, elija Crear nueva billetera, luego seleccione Bitcoin) e INMEDIATAMENTE mueva su BTC allí. Las nuevas carteras BTC (24 palabras) de Cake son seguras",
|
||||||
"outgoing": "Saliente",
|
"outgoing": "Saliente",
|
||||||
"outputs": "Salidas",
|
"outputs": "Salidas",
|
||||||
"overwrite_amount": "Overwrite amount",
|
"overwrite_amount": "Sobreescribir monto",
|
||||||
"pairingInvalidEvent": "Evento de emparejamiento no válido",
|
"pairingInvalidEvent": "Evento de emparejamiento no válido",
|
||||||
"passphrase": "Passfrase (opcional)",
|
"passphrase": "Passfrase (opcional)",
|
||||||
"passphrases_doesnt_match": "Las frases de contrato no coinciden, intente nuevamente",
|
"passphrases_doesnt_match": "Las frases de contrato no coinciden, intente nuevamente",
|
||||||
|
@ -482,18 +482,18 @@
|
||||||
"pin_is_incorrect": "PIN es incorrecto",
|
"pin_is_incorrect": "PIN es incorrecto",
|
||||||
"pin_number": "Número PIN",
|
"pin_number": "Número PIN",
|
||||||
"placeholder_contacts": "Tus contactos se mostrarán aquí",
|
"placeholder_contacts": "Tus contactos se mostrarán aquí",
|
||||||
"placeholder_transactions": "Sus transacciones se mostrarán aquí",
|
"placeholder_transactions": "Tus transacciones se mostrarán aquí",
|
||||||
"please_fill_totp": "Complete el código de 8 dígitos presente en su otro dispositivo",
|
"please_fill_totp": "Completa el código de 8 dígitos presente en su otro dispositivo",
|
||||||
"please_make_selection": "Seleccione a continuación para crear o recuperar su billetera.",
|
"please_make_selection": "Selecciona a continuación para crear o recuperar su billetera.",
|
||||||
"please_reference_document": "Consulte los documentos a continuación para obtener más información.",
|
"please_reference_document": "Consulta los documentos a continuación para obtener más información.",
|
||||||
"please_select": "Por favor seleccione:",
|
"please_select": "Por favor selecciona:",
|
||||||
"please_select_backup_file": "Seleccione el archivo de respaldo e ingrese la contraseña de respaldo.",
|
"please_select_backup_file": "Selecciona el archivo de respaldo e ingrese la contraseña de respaldo.",
|
||||||
"please_try_to_connect_to_another_node": "Intenta conectarte a otro nodo",
|
"please_try_to_connect_to_another_node": "Intenta conectarte a otro nodo",
|
||||||
"please_wait": "Espere por favor",
|
"please_wait": "Espera por favor",
|
||||||
"polygonscan_history": "Historial de PolygonScan",
|
"polygonscan_history": "Historial de PolygonScan",
|
||||||
"powered_by": "Energizado por ${title}",
|
"powered_by": "Posible gracias a ${title}",
|
||||||
"pre_seed_button_text": "Entiendo. Muéstrame mi semilla",
|
"pre_seed_button_text": "Entiendo. Muéstrame mi semilla",
|
||||||
"pre_seed_description": "En la página siguiente verá una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es SU responsabilidad escribirlo y guardarlo en un lugar seguro fuera de la aplicación Cake Wallet.",
|
"pre_seed_description": "En la página siguiente verás una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar tu billetera en caso de pérdida o mal funcionamiento. Es TU responsabilidad escribirla y guardarla en un lugar seguro fuera de la aplicación Cake Wallet.",
|
||||||
"pre_seed_title": "IMPORTANTE",
|
"pre_seed_title": "IMPORTANTE",
|
||||||
"prepaid_cards": "Tajetas prepagadas",
|
"prepaid_cards": "Tajetas prepagadas",
|
||||||
"prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla",
|
"prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla",
|
||||||
|
@ -502,17 +502,17 @@
|
||||||
"privacy_settings": "Configuración de privacidad",
|
"privacy_settings": "Configuración de privacidad",
|
||||||
"private_key": "Clave privada",
|
"private_key": "Clave privada",
|
||||||
"proceed_after_one_minute": "Si la pantalla no continúa después de 1 minuto, revisa tu correo electrónico.",
|
"proceed_after_one_minute": "Si la pantalla no continúa después de 1 minuto, revisa tu correo electrónico.",
|
||||||
"proceed_on_device": "Continúe con su dispositivo",
|
"proceed_on_device": "Continúa con tu dispositivo",
|
||||||
"proceed_on_device_description": "Siga las instrucciones solicitadas en su billetera de hardware",
|
"proceed_on_device_description": "Sigue las instrucciones solicitadas en su billetera de hardware",
|
||||||
"profile": "Perfil",
|
"profile": "Perfil",
|
||||||
"provider_error": "${provider} error",
|
"provider_error": "${provider} error",
|
||||||
"public_key": "Clave pública",
|
"public_key": "Clave pública",
|
||||||
"purchase_gift_card": "Comprar tarjeta de regalo",
|
"purchase_gift_card": "Comprar tarjeta de regalo",
|
||||||
"purple_dark_theme": "Tema morado oscuro",
|
"purple_dark_theme": "Tema morado oscuro",
|
||||||
"qr_fullscreen": "Toque para abrir el código QR en pantalla completa",
|
"qr_fullscreen": "Toque para abrir el código QR en pantalla completa",
|
||||||
"qr_payment_amount": "This QR code contains a payment amount. Do you want to overwrite the current value?",
|
"qr_payment_amount": "Este código QR contiene un monto de pago. ¿Quieres sobreescribirlo?",
|
||||||
"quantity": "Cantidad",
|
"quantity": "Cantidad",
|
||||||
"question_to_disable_2fa": "¿Está seguro de que desea deshabilitar Cake 2FA? Ya no se necesitará un código 2FA para acceder a la billetera y a ciertas funciones.",
|
"question_to_disable_2fa": "¿Estás seguro de que desea deshabilitar Cake 2FA? Ya no se necesitará un código 2FA para acceder a la billetera y a ciertas funciones.",
|
||||||
"receivable_balance": "Saldo de cuentas por cobrar",
|
"receivable_balance": "Saldo de cuentas por cobrar",
|
||||||
"receive": "Recibir",
|
"receive": "Recibir",
|
||||||
"receive_amount": "Cantidad",
|
"receive_amount": "Cantidad",
|
||||||
|
@ -529,12 +529,12 @@
|
||||||
"remaining": "restante",
|
"remaining": "restante",
|
||||||
"remove": "Retirar",
|
"remove": "Retirar",
|
||||||
"remove_node": "Eliminar nodo",
|
"remove_node": "Eliminar nodo",
|
||||||
"remove_node_message": "¿Está seguro de que desea eliminar el nodo seleccionado?",
|
"remove_node_message": "¿Estás seguro de que desea eliminar el nodo seleccionado?",
|
||||||
"rename": "Rebautizar",
|
"rename": "Renombrar",
|
||||||
"rep_warning": "Advertencia representativa",
|
"rep_warning": "Advertencia representativa",
|
||||||
"rep_warning_sub": "Su representante no parece estar en buena posición. Toque aquí para seleccionar uno nuevo",
|
"rep_warning_sub": "Tu representante no parece estar en buena posición. Toca aquí para seleccionar uno nuevo",
|
||||||
"repeat_wallet_password": "Repita la contraseña de billetera",
|
"repeat_wallet_password": "Repite la contraseña de billetera",
|
||||||
"repeated_password_is_incorrect": "La contraseña repetida es incorrecta. Repita la contraseña de la billetera nuevamente.",
|
"repeated_password_is_incorrect": "La contraseña repetida es incorrecta. Repite la contraseña de la billetera nuevamente.",
|
||||||
"require_for_adding_contacts": "Requerido para agregar contactos",
|
"require_for_adding_contacts": "Requerido para agregar contactos",
|
||||||
"require_for_all_security_and_backup_settings": "Requerido para todas las configuraciones de seguridad y copia de seguridad",
|
"require_for_all_security_and_backup_settings": "Requerido para todas las configuraciones de seguridad y copia de seguridad",
|
||||||
"require_for_assessing_wallet": "Requerido para acceder a la billetera",
|
"require_for_assessing_wallet": "Requerido para acceder a la billetera",
|
||||||
|
@ -566,17 +566,17 @@
|
||||||
"restore_recover": "Recuperar",
|
"restore_recover": "Recuperar",
|
||||||
"restore_restore_wallet": "Recuperar Cartera",
|
"restore_restore_wallet": "Recuperar Cartera",
|
||||||
"restore_seed_keys_restore": "Restauración de semillas / llaves",
|
"restore_seed_keys_restore": "Restauración de semillas / llaves",
|
||||||
"restore_spend_key_private": "Spend clave (privado)",
|
"restore_spend_key_private": "Llave de gasto (privada)",
|
||||||
"restore_title_from_backup": "Restaurar desde un archivo de respaldo",
|
"restore_title_from_backup": "Restaurar desde un archivo de respaldo",
|
||||||
"restore_title_from_hardware_wallet": "Restaurar desde la billetera de hardware",
|
"restore_title_from_hardware_wallet": "Restaurar desde una cartera fría",
|
||||||
"restore_title_from_keys": "De las claves",
|
"restore_title_from_keys": "Usando las claves",
|
||||||
"restore_title_from_seed": "De la semilla",
|
"restore_title_from_seed": "Usando la semilla",
|
||||||
"restore_title_from_seed_keys": "Restaurar desde semilla/claves",
|
"restore_title_from_seed_keys": "Restaurar usando semilla/claves",
|
||||||
"restore_view_key_private": "View clave (privado)",
|
"restore_view_key_private": "Llave de vista (privado)",
|
||||||
"restore_wallet": "Restaurar billetera",
|
"restore_wallet": "Restaurar billetera",
|
||||||
"restore_wallet_name": "Nombre de la billetera",
|
"restore_wallet_name": "Nombre de la billetera",
|
||||||
"restore_wallet_restore_description": "Restaurar billetera",
|
"restore_wallet_restore_description": "Restaurar billetera",
|
||||||
"robinhood_option_description": "Compre y transfiera instantáneamente utilizando su tarjeta de débito, cuenta bancaria o saldo de Robinhood. Solo EE. UU.",
|
"robinhood_option_description": "Compra y transfiere instantáneamente utilizando su tarjeta de débito, cuenta bancaria o saldo de Robinhood. Solo EE. UU.",
|
||||||
"router_no_route": "No hay ruta definida para ${name}",
|
"router_no_route": "No hay ruta definida para ${name}",
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"save_backup_password": "Asegúrese de haber guardado su contraseña de respaldo. No podrá importar sus archivos de respaldo sin él.",
|
"save_backup_password": "Asegúrese de haber guardado su contraseña de respaldo. No podrá importar sus archivos de respaldo sin él.",
|
||||||
|
@ -585,7 +585,7 @@
|
||||||
"saved_the_trade_id": "He salvado comercial ID",
|
"saved_the_trade_id": "He salvado comercial ID",
|
||||||
"scan_one_block": "Escanear un bloque",
|
"scan_one_block": "Escanear un bloque",
|
||||||
"scan_qr_code": "Escanear código QR",
|
"scan_qr_code": "Escanear código QR",
|
||||||
"scan_qr_code_to_get_address": "Escanee el código QR para obtener la dirección",
|
"scan_qr_code_to_get_address": "Escanea el código QR para obtener la dirección",
|
||||||
"scan_qr_on_device": "Escanea este código QR en otro dispositivo",
|
"scan_qr_on_device": "Escanea este código QR en otro dispositivo",
|
||||||
"search": "Búsqueda",
|
"search": "Búsqueda",
|
||||||
"search_add_token": "Buscar/Agregar token",
|
"search_add_token": "Buscar/Agregar token",
|
||||||
|
@ -612,25 +612,25 @@
|
||||||
"seed_language_german": "Alemán",
|
"seed_language_german": "Alemán",
|
||||||
"seed_language_italian": "Italiana/Italiano",
|
"seed_language_italian": "Italiana/Italiano",
|
||||||
"seed_language_japanese": "Japonés",
|
"seed_language_japanese": "Japonés",
|
||||||
"seed_language_korean": "coreano",
|
"seed_language_korean": "Coreano",
|
||||||
"seed_language_next": "Próximo",
|
"seed_language_next": "Próximo",
|
||||||
"seed_language_portuguese": "Portugués",
|
"seed_language_portuguese": "Portugués",
|
||||||
"seed_language_russian": "Ruso",
|
"seed_language_russian": "Ruso",
|
||||||
"seed_language_spanish": "Español",
|
"seed_language_spanish": "Español",
|
||||||
"seed_phrase_length": "Longitud de la frase inicial",
|
"seed_phrase_length": "Longitud de la frase inicial",
|
||||||
"seed_reminder": "Anótelos en caso de que pierda o borre su teléfono",
|
"seed_reminder": "Anótalos en caso de que pierdas o borres tu aplicación",
|
||||||
"seed_share": "Compartir semillas",
|
"seed_share": "Compartir semillas",
|
||||||
"seed_title": "Semilla",
|
"seed_title": "Semilla",
|
||||||
"seedtype": "Type de semillas",
|
"seedtype": "Tipos de semillas",
|
||||||
"seedtype_alert_content": "Compartir semillas con otras billeteras solo es posible con Bip39 Seed Type.",
|
"seedtype_alert_content": "Compartir semillas con otras billeteras solo es posible con semillas bip39 - un tipo específico de semilla.",
|
||||||
"seedtype_alert_title": "Alerta de type de semillas",
|
"seedtype_alert_title": "Alerta de tipo de semillas",
|
||||||
"seedtype_legacy": "Legado (25 palabras)",
|
"seedtype_legacy": "Semilla clásica-legacy (25 palabras)",
|
||||||
"seedtype_polyseed": "Polieta (16 palabras)",
|
"seedtype_polyseed": "Poli-semilla (16 palabras)",
|
||||||
"seedtype_wownero": "Wownero (14 palabras)",
|
"seedtype_wownero": "Wownero (14 palabras)",
|
||||||
"select_backup_file": "Seleccionar archivo de respaldo",
|
"select_backup_file": "Seleccionar archivo de respaldo",
|
||||||
"select_buy_provider_notice": "Seleccione un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.",
|
"select_buy_provider_notice": "Selecciona un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.",
|
||||||
"select_destination": "Seleccione el destino del archivo de copia de seguridad.",
|
"select_destination": "Selecciona el destino del archivo de copia de seguridad.",
|
||||||
"select_sell_provider_notice": "Seleccione un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.",
|
"select_sell_provider_notice": "Selecciona un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.",
|
||||||
"sell": "Vender",
|
"sell": "Vender",
|
||||||
"sell_alert_content": "Actualmente solo admitimos la venta de Bitcoin, Ethereum y Litecoin. Cree o cambie a su billetera Bitcoin, Ethereum o Litecoin.",
|
"sell_alert_content": "Actualmente solo admitimos la venta de Bitcoin, Ethereum y Litecoin. Cree o cambie a su billetera Bitcoin, Ethereum o Litecoin.",
|
||||||
"sell_monero_com_alert_content": "Aún no se admite la venta de Monero",
|
"sell_monero_com_alert_content": "Aún no se admite la venta de Monero",
|
||||||
|
@ -676,16 +676,16 @@
|
||||||
"settings_only_transactions": "Solo transacciones",
|
"settings_only_transactions": "Solo transacciones",
|
||||||
"settings_personal": "Personal",
|
"settings_personal": "Personal",
|
||||||
"settings_save_recipient_address": "Guardar dirección del destinatario",
|
"settings_save_recipient_address": "Guardar dirección del destinatario",
|
||||||
"settings_support": "Apoyo",
|
"settings_support": "Ayuda",
|
||||||
"settings_terms_and_conditions": "Términos y Condiciones",
|
"settings_terms_and_conditions": "Términos y Condiciones",
|
||||||
"settings_title": "Configuraciones",
|
"settings_title": "Configuraciones",
|
||||||
"settings_trades": "Comercia",
|
"settings_trades": "Comercia",
|
||||||
"settings_transactions": "Transacciones",
|
"settings_transactions": "Transacciones",
|
||||||
"settings_wallets": "Carteras",
|
"settings_wallets": "Carteras",
|
||||||
"setup_2fa": "Configurar pastel 2FA",
|
"setup_2fa": "Configurar 2FA",
|
||||||
"setup_2fa_text": "Cake 2FA funciona utilizando TOTP como segundo factor de autenticación.\n\nEl TOTP de Cake 2FA requiere SHA-512 y soporte de 8 dígitos; esto proporciona una mayor seguridad. Puede encontrar más información y aplicaciones compatibles en la guía.",
|
"setup_2fa_text": "Cake 2FA funciona utilizando TOTP como segundo factor de autenticación.\n\nEl TOTP de Cake 2FA requiere SHA-512 y soporte de 8 dígitos; esto proporciona una mayor seguridad. Puedes encontrar más información y aplicaciones compatibles en la guía.",
|
||||||
"setup_pin": "PIN de configuración",
|
"setup_pin": "PIN de configuración",
|
||||||
"setup_successful": "Su PIN se ha configurado correctamente!",
|
"setup_successful": "Tu PIN se ha configurado correctamente!",
|
||||||
"setup_totp_recommended": "Configurar TOTP",
|
"setup_totp_recommended": "Configurar TOTP",
|
||||||
"setup_warning_2fa_text": "Deberá restaurar su billetera a partir de la semilla mnemotécnica.\n\nEl soporte de Cake no podrá ayudarlo si pierde el acceso a su 2FA o a sus semillas mnemotécnicas.\nCake 2FA es una segunda autenticación para ciertas acciones en la billetera. Antes de usar Cake 2FA, recomendamos leer la guía.NO es tan seguro como el almacenamiento en frío.\n\nSi pierde el acceso a su aplicación 2FA o a sus claves TOTP, perderá el acceso a esta billetera. ",
|
"setup_warning_2fa_text": "Deberá restaurar su billetera a partir de la semilla mnemotécnica.\n\nEl soporte de Cake no podrá ayudarlo si pierde el acceso a su 2FA o a sus semillas mnemotécnicas.\nCake 2FA es una segunda autenticación para ciertas acciones en la billetera. Antes de usar Cake 2FA, recomendamos leer la guía.NO es tan seguro como el almacenamiento en frío.\n\nSi pierde el acceso a su aplicación 2FA o a sus claves TOTP, perderá el acceso a esta billetera. ",
|
||||||
"setup_your_debit_card": "Configura tu tarjeta de débito",
|
"setup_your_debit_card": "Configura tu tarjeta de débito",
|
||||||
|
@ -704,23 +704,24 @@
|
||||||
"signature": "Firma",
|
"signature": "Firma",
|
||||||
"signature_invalid_error": "La firma no es válida para el mensaje dado",
|
"signature_invalid_error": "La firma no es válida para el mensaje dado",
|
||||||
"signTransaction": "Firmar transacción",
|
"signTransaction": "Firmar transacción",
|
||||||
"signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.",
|
"signup_for_card_accept_terms": "Regístrate para obtener la tarjeta y acepte los términos.",
|
||||||
"silent_payment": "Pago silencioso",
|
"silent_payment": "Pago silencioso",
|
||||||
"silent_payments": "Pagos silenciosos",
|
"silent_payments": "Pagos silenciosos",
|
||||||
"silent_payments_always_scan": "Establecer pagos silenciosos siempre escaneando",
|
"silent_payments_always_scan": "Establecer pagos silenciosos siempre escaneando",
|
||||||
"silent_payments_disclaimer": "Las nuevas direcciones no son nuevas identidades. Es una reutilización de una identidad existente con una etiqueta diferente.",
|
"silent_payments_disclaimer": "Las nuevas direcciones no son nuevas identidades. Es una reutilización de una identidad existente con una etiqueta diferente.",
|
||||||
"silent_payments_display_card": "Mostrar tarjeta de pagos silenciosos",
|
"silent_payments_display_card": "Mostrar tarjeta de pagos silenciosos",
|
||||||
|
"silent_payments_register_key": "Clave de vista de registro para escaneo más rápido",
|
||||||
"silent_payments_scan_from_date": "Escanear desde la fecha",
|
"silent_payments_scan_from_date": "Escanear desde la fecha",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Ingrese la altura del bloque que desea comenzar a escanear para pagos silenciosos entrantes, o use la fecha en su lugar. Puede elegir si la billetera continúa escaneando cada bloque, o verifica solo la altura especificada.",
|
"silent_payments_scan_from_date_or_blockheight": "Ingresa la altura de bloque que desea comenzar a escanear para pagos silenciosos entrantes, o usa la fecha en su lugar. Puedes elegir si la billetera continúa escaneando cada bloque, o verifica solo la altura especificada.",
|
||||||
"silent_payments_scan_from_height": "Escanear desde la altura del bloque",
|
"silent_payments_scan_from_height": "Escanear desde la altura de bloque específico",
|
||||||
"silent_payments_scanned_tip": "Escaneado hasta la punta! (${tip})",
|
"silent_payments_scanned_tip": "Escaneado hasta la altura actual! (${tip})",
|
||||||
"silent_payments_scanning": "Escaneo de pagos silenciosos",
|
"silent_payments_scanning": "Escaneo de pagos silenciosos",
|
||||||
"silent_payments_settings": "Configuración de pagos silenciosos",
|
"silent_payments_settings": "Configuración de pagos silenciosos",
|
||||||
"single_seed_wallets_group": "Billeteras de semillas individuales",
|
"single_seed_wallets_group": "Billeteras de semillas individuales",
|
||||||
"slidable": "deslizable",
|
"slidable": "deslizable",
|
||||||
"sort_by": "Ordenar por",
|
"sort_by": "Ordenar por",
|
||||||
"spend_key_private": "Spend clave (privado)",
|
"spend_key_private": "Llave de gasto (privada)",
|
||||||
"spend_key_public": "Spend clave (público)",
|
"spend_key_public": "Llave de gasto (pública)",
|
||||||
"status": "Estado: ",
|
"status": "Estado: ",
|
||||||
"string_default": "Por defecto",
|
"string_default": "Por defecto",
|
||||||
"subaddress_title": "Lista de subdirecciones",
|
"subaddress_title": "Lista de subdirecciones",
|
||||||
|
@ -729,14 +730,14 @@
|
||||||
"successful": "Exitoso",
|
"successful": "Exitoso",
|
||||||
"support_description_guides": "Documentación y apoyo para problemas comunes",
|
"support_description_guides": "Documentación y apoyo para problemas comunes",
|
||||||
"support_description_live_chat": "¡GRATIS y RÁPIDO! Los representantes de apoyo capacitado están disponibles para ayudar",
|
"support_description_live_chat": "¡GRATIS y RÁPIDO! Los representantes de apoyo capacitado están disponibles para ayudar",
|
||||||
"support_description_other_links": "Únase a nuestras comunidades o comuníquese con nosotros nuestros socios a través de otros métodos",
|
"support_description_other_links": "Únete a nuestras comunidades o comunícate con nosotros nuestros socios a través de otros métodos",
|
||||||
"support_title_guides": "Guías de billetera para pastel",
|
"support_title_guides": "Guías de billetera para pastel",
|
||||||
"support_title_live_chat": "Soporte vital",
|
"support_title_live_chat": "Soporte en tiempo real",
|
||||||
"support_title_other_links": "Otros enlaces de soporte",
|
"support_title_other_links": "Otros enlaces de soporte",
|
||||||
"sweeping_wallet": "Billetera de barrido",
|
"sweeping_wallet": "Barrer billetera (gastar todos los fondos disponibles)",
|
||||||
"sweeping_wallet_alert": "Esto no debería llevar mucho tiempo. NO DEJES ESTA PANTALLA O SE PUEDEN PERDER LOS FONDOS BARRIDOS",
|
"sweeping_wallet_alert": "Esto no debería llevar mucho tiempo. NO DEJES ESTA PANTALLA O SE PUEDEN PERDER LOS FONDOS BARRIDOS",
|
||||||
"switchToETHWallet": "Cambie a una billetera Ethereum e inténtelo nuevamente.",
|
"switchToETHWallet": "Cambia a una billetera Ethereum e inténtelo nuevamente.",
|
||||||
"switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)",
|
"switchToEVMCompatibleWallet": "Cambia a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)",
|
||||||
"symbol": "Símbolo",
|
"symbol": "Símbolo",
|
||||||
"sync_all_wallets": "Sincronizar todas las billeteras",
|
"sync_all_wallets": "Sincronizar todas las billeteras",
|
||||||
"sync_status_attempting_scan": "Intento de escaneo",
|
"sync_status_attempting_scan": "Intento de escaneo",
|
||||||
|
@ -759,7 +760,7 @@
|
||||||
"third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!",
|
"third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!",
|
||||||
"third_intro_title": "Yat juega muy bien con otras",
|
"third_intro_title": "Yat juega muy bien con otras",
|
||||||
"thorchain_contract_address_not_supported": "Thorchain no admite enviar a una dirección de contrato",
|
"thorchain_contract_address_not_supported": "Thorchain no admite enviar a una dirección de contrato",
|
||||||
"thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambie la dirección o seleccione un proveedor diferente.",
|
"thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambia la dirección o selecciona un proveedor diferente.",
|
||||||
"time": "${minutes}m ${seconds}s",
|
"time": "${minutes}m ${seconds}s",
|
||||||
"tip": "Consejo:",
|
"tip": "Consejo:",
|
||||||
"today": "Hoy",
|
"today": "Hoy",
|
||||||
|
@ -772,8 +773,8 @@
|
||||||
"tor_only": "solo Tor",
|
"tor_only": "solo Tor",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"total_saving": "Ahorro Total",
|
"total_saving": "Ahorro Total",
|
||||||
"totp_2fa_failure": "Código incorrecto. Intente con un código diferente o genere una nueva clave secreta. Use una aplicación 2FA compatible que admita códigos de 8 dígitos y SHA512.",
|
"totp_2fa_failure": "Código incorrecto. Intente con un código diferente o genere una nueva clave secreta. Usa una aplicación 2FA compatible que admita códigos de 8 dígitos y SHA512.",
|
||||||
"totp_2fa_success": "¡Éxito! Cake 2FA habilitado para esta billetera. Recuerde guardar su semilla mnemotécnica en caso de que pierda el acceso a la billetera.",
|
"totp_2fa_success": "¡Éxito! Cake 2FA habilitado para esta billetera. Recuerda guardar tu semilla mnemotécnica en caso de que pierdas el acceso a la billetera.",
|
||||||
"totp_auth_url": "URL de autenticación TOTP",
|
"totp_auth_url": "URL de autenticación TOTP",
|
||||||
"totp_code": "Código TOTP",
|
"totp_code": "Código TOTP",
|
||||||
"totp_secret_code": "Código secreto TOTP",
|
"totp_secret_code": "Código secreto TOTP",
|
||||||
|
@ -826,21 +827,21 @@
|
||||||
"transaction_priority_slow": "Lento",
|
"transaction_priority_slow": "Lento",
|
||||||
"transaction_sent": "Transacción enviada!",
|
"transaction_sent": "Transacción enviada!",
|
||||||
"transaction_sent_notice": "Si la pantalla no continúa después de 1 minuto, revisa un explorador de bloques y tu correo electrónico.",
|
"transaction_sent_notice": "Si la pantalla no continúa después de 1 minuto, revisa un explorador de bloques y tu correo electrónico.",
|
||||||
"transactions": "Actas",
|
"transactions": "Transacciones",
|
||||||
"transactions_by_date": "Transacciones por fecha",
|
"transactions_by_date": "Transacciones por fecha",
|
||||||
"trongrid_history": "Historia trongrid",
|
"trongrid_history": "Historia trongrid",
|
||||||
"trusted": "de confianza",
|
"trusted": "de confianza",
|
||||||
"tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.",
|
"tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.",
|
||||||
"tx_commit_failed": "La confirmación de transacción falló. Póngase en contacto con el soporte.",
|
"tx_commit_failed": "La confirmación de transacción falló. Ponte en contacto con el soporte.",
|
||||||
"tx_commit_failed_no_peers": "La transacción no se transmitió, intente nuevamente en un segundo más o menos",
|
"tx_commit_failed_no_peers": "La transacción no se transmitió, intenta nuevamente en un segundo más o menos",
|
||||||
"tx_invalid_input": "Está utilizando el tipo de entrada incorrecto para este tipo de pago",
|
"tx_invalid_input": "Está utilizando el tipo de entrada incorrecto para este tipo de pago",
|
||||||
"tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.",
|
"tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.",
|
||||||
"tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas",
|
"tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Selecciona más bajo control de monedas",
|
||||||
"tx_rejected_bip68_final": "La transacción tiene entradas no confirmadas y no ha podido reemplazar por tarifa.",
|
"tx_rejected_bip68_final": "La transacción tiene entradas no confirmadas y no ha podido reemplazar por tarifa.",
|
||||||
"tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intente enviar todo o reducir la cantidad.",
|
"tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intenta enviar todo o reducir la cantidad.",
|
||||||
"tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumente la cantidad.",
|
"tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumenta la cantidad.",
|
||||||
"tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.",
|
"tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.",
|
||||||
"tx_rejected_vout_negative": "No es suficiente saldo para pagar las tarifas de esta transacción. Verifique el saldo de monedas bajo control de monedas.",
|
"tx_rejected_vout_negative": "No es suficiente saldo para pagar las tarifas de esta transacción. Verifica el saldo de monedas bajo control de monedas.",
|
||||||
"tx_wrong_balance_exception": "No tiene suficiente ${currency} para enviar esta cantidad.",
|
"tx_wrong_balance_exception": "No tiene suficiente ${currency} para enviar esta cantidad.",
|
||||||
"tx_wrong_balance_with_amount_exception": "No tiene suficiente ${currency} para enviar la cantidad total de ${amount}",
|
"tx_wrong_balance_with_amount_exception": "No tiene suficiente ${currency} para enviar la cantidad total de ${amount}",
|
||||||
"tx_zero_fee_exception": "No se puede enviar transacciones con 0 tarifa. Intente aumentar la tasa o verificar su conexión para las últimas estimaciones.",
|
"tx_zero_fee_exception": "No se puede enviar transacciones con 0 tarifa. Intente aumentar la tasa o verificar su conexión para las últimas estimaciones.",
|
||||||
|
@ -849,7 +850,7 @@
|
||||||
"unconfirmed": "Saldo no confirmado",
|
"unconfirmed": "Saldo no confirmado",
|
||||||
"understand": "Entiendo",
|
"understand": "Entiendo",
|
||||||
"unlock": "desbloquear",
|
"unlock": "desbloquear",
|
||||||
"unmatched_currencies": "La moneda de su billetera actual no coincide con la del QR escaneado",
|
"unmatched_currencies": "La moneda de tu billetera actual no coincide con la del QR escaneado",
|
||||||
"unspent_change": "Cambiar",
|
"unspent_change": "Cambiar",
|
||||||
"unspent_coins_details_title": "Detalles de monedas no gastadas",
|
"unspent_coins_details_title": "Detalles de monedas no gastadas",
|
||||||
"unspent_coins_title": "Monedas no gastadas",
|
"unspent_coins_title": "Monedas no gastadas",
|
||||||
|
@ -858,11 +859,11 @@
|
||||||
"upto": "hasta ${value}",
|
"upto": "hasta ${value}",
|
||||||
"usb": "USB",
|
"usb": "USB",
|
||||||
"use": "Utilizar a ",
|
"use": "Utilizar a ",
|
||||||
"use_card_info_three": "Utilice la tarjeta digital en línea o con métodos de pago sin contacto.",
|
"use_card_info_three": "Utiliza la tarjeta digital en línea o con métodos de pago sin contacto.",
|
||||||
"use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
|
"use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
|
||||||
"use_ssl": "Utilice SSL",
|
"use_ssl": "Utiliza SSL",
|
||||||
"use_suggested": "Usar sugerido",
|
"use_suggested": "Usar sugerido",
|
||||||
"use_testnet": "Use TestNet",
|
"use_testnet": "Usar TestNet",
|
||||||
"value": "Valor",
|
"value": "Valor",
|
||||||
"value_type": "Tipo de valor",
|
"value_type": "Tipo de valor",
|
||||||
"variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
|
"variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
|
||||||
|
@ -871,17 +872,17 @@
|
||||||
"verify_with_2fa": "Verificar con Cake 2FA",
|
"verify_with_2fa": "Verificar con Cake 2FA",
|
||||||
"version": "Versión ${currentVersion}",
|
"version": "Versión ${currentVersion}",
|
||||||
"view_all": "Ver todo",
|
"view_all": "Ver todo",
|
||||||
"view_in_block_explorer": "View in Block Explorer",
|
"view_in_block_explorer": "Ver en explorador de bloques",
|
||||||
"view_key_private": "View clave (privado)",
|
"view_key_private": "Llave de vista (privada)",
|
||||||
"view_key_public": "View clave (público)",
|
"view_key_public": "Llave de vista (pública)",
|
||||||
"view_transaction_on": "View Transaction on ",
|
"view_transaction_on": "Ver transacción en ",
|
||||||
"voting_weight": "Peso de votación",
|
"voting_weight": "Peso de votación",
|
||||||
"waitFewSecondForTxUpdate": "Espere unos segundos para que la transacción se refleje en el historial de transacciones.",
|
"waitFewSecondForTxUpdate": "Espera unos segundos para que la transacción se refleje en el historial de transacciones.",
|
||||||
"wallet_group": "Grupo de billetera",
|
"wallet_group": "Grupo de billetera",
|
||||||
"wallet_group_description_four": "para crear una billetera con una semilla completamente nueva.",
|
"wallet_group_description_four": "Para crear una billetera con una semilla completamente nueva.",
|
||||||
"wallet_group_description_one": "En la billetera de pastel, puedes crear un",
|
"wallet_group_description_one": "En la billetera de pastel, puedes crear un",
|
||||||
"wallet_group_description_three": "Para ver las billeteras disponibles y/o la pantalla de grupos de billeteras. O elegir",
|
"wallet_group_description_three": "Para ver las billeteras disponibles y/o la pantalla de grupos de billeteras. O elegir",
|
||||||
"wallet_group_description_two": "seleccionando una billetera existente para compartir una semilla con. Cada grupo de billetera puede contener una sola billetera de cada tipo de moneda. \n\n puede seleccionar",
|
"wallet_group_description_two": "Seleccionando una billetera existente para compartir una semilla con. Cada grupo de billetera puede contener una sola billetera de cada tipo de moneda. \n\n puedes seleccionar",
|
||||||
"wallet_group_empty_state_text_one": "Parece que no tienes ningún grupo de billetera compatible !\n\n toque",
|
"wallet_group_empty_state_text_one": "Parece que no tienes ningún grupo de billetera compatible !\n\n toque",
|
||||||
"wallet_group_empty_state_text_two": "a continuación para hacer uno nuevo.",
|
"wallet_group_empty_state_text_two": "a continuación para hacer uno nuevo.",
|
||||||
"wallet_keys": "Billetera semilla/claves",
|
"wallet_keys": "Billetera semilla/claves",
|
||||||
|
@ -889,7 +890,7 @@
|
||||||
"wallet_list_edit_group_name": "Editar nombre de grupo",
|
"wallet_list_edit_group_name": "Editar nombre de grupo",
|
||||||
"wallet_list_edit_wallet": "Editar billetera",
|
"wallet_list_edit_wallet": "Editar billetera",
|
||||||
"wallet_list_failed_to_load": "No se pudo cargar ${wallet_name} la billetera. ${error}",
|
"wallet_list_failed_to_load": "No se pudo cargar ${wallet_name} la billetera. ${error}",
|
||||||
"wallet_list_failed_to_remove": "Error al elimina ${wallet_name} billetera. ${error}",
|
"wallet_list_failed_to_remove": "Error al eliminar ${wallet_name} billetera. ${error}",
|
||||||
"wallet_list_load_wallet": "Billetera de carga",
|
"wallet_list_load_wallet": "Billetera de carga",
|
||||||
"wallet_list_loading_wallet": "Billetera ${wallet_name} de carga",
|
"wallet_list_loading_wallet": "Billetera ${wallet_name} de carga",
|
||||||
"wallet_list_removing_wallet": "Retirar ${wallet_name} billetera",
|
"wallet_list_removing_wallet": "Retirar ${wallet_name} billetera",
|
||||||
|
@ -898,8 +899,8 @@
|
||||||
"wallet_list_wallet_name": "Nombre de la billetera",
|
"wallet_list_wallet_name": "Nombre de la billetera",
|
||||||
"wallet_menu": "Menú de billetera",
|
"wallet_menu": "Menú de billetera",
|
||||||
"wallet_name": "Nombre de la billetera",
|
"wallet_name": "Nombre de la billetera",
|
||||||
"wallet_name_exists": "Wallet con ese nombre ya ha existido",
|
"wallet_name_exists": "Cartera con ese nombre ya existe, escoge otro",
|
||||||
"wallet_password_is_empty": "La contraseña de billetera está vacía. La contraseña de la billetera no debe estar vacía",
|
"wallet_password_is_empty": "La contraseña de billetera está vacía. La contraseña de la billetera no puede estar vacía",
|
||||||
"wallet_recovery_height": "Altura de recuperación",
|
"wallet_recovery_height": "Altura de recuperación",
|
||||||
"wallet_restoration_store_incorrect_seed_length": "Longitud de semilla incorrecta",
|
"wallet_restoration_store_incorrect_seed_length": "Longitud de semilla incorrecta",
|
||||||
"wallet_seed": "Semilla de billetera",
|
"wallet_seed": "Semilla de billetera",
|
||||||
|
@ -913,30 +914,30 @@
|
||||||
"what_is_silent_payments": "¿Qué son los pagos silenciosos?",
|
"what_is_silent_payments": "¿Qué son los pagos silenciosos?",
|
||||||
"widgets_address": "Dirección",
|
"widgets_address": "Dirección",
|
||||||
"widgets_or": "o",
|
"widgets_or": "o",
|
||||||
"widgets_restore_from_blockheight": "Restaurar desde blockheight",
|
"widgets_restore_from_blockheight": "Restaurar desde altura de bloque",
|
||||||
"widgets_restore_from_date": "Restaurar desde fecha",
|
"widgets_restore_from_date": "Restaurar desde fecha",
|
||||||
"widgets_seed": "Semilla",
|
"widgets_seed": "Semilla",
|
||||||
"wouoldLikeToConnect": "quisiera conectar",
|
"wouoldLikeToConnect": "quisiera conectar",
|
||||||
"write_down_backup_password": "Escriba su contraseña de respaldo, que se utiliza para la importación de sus archivos de respaldo.",
|
"write_down_backup_password": "Escribe su contraseña de respaldo, que se utiliza para la importación de sus archivos de respaldo.",
|
||||||
"xlm_extra_info": "No olvide especificar el ID de nota al enviar la transacción XLM para el intercambio",
|
"xlm_extra_info": "No olvides especificar el ID de nota al enviar la transacción XLM para el intercambio",
|
||||||
"xmr_available_balance": "Saldo disponible",
|
"xmr_available_balance": "Saldo disponible",
|
||||||
"xmr_full_balance": "Balance total",
|
"xmr_full_balance": "Balance total",
|
||||||
"xmr_hidden": "Oculto",
|
"xmr_hidden": "Oculto",
|
||||||
"xmr_to_error": "Error de XMR.TO",
|
"xmr_to_error": "Error de XMR.TO",
|
||||||
"xmr_to_error_description": "Monto invalido. Límite máximo de 8 dígitos después del punto decimal",
|
"xmr_to_error_description": "Monto invalido. Límite máximo de 8 dígitos después del punto decimal",
|
||||||
"xrp_extra_info": "No olvide especificar la etiqueta de destino al enviar la transacción XRP para el intercambio",
|
"xrp_extra_info": "No olvides especificar la etiqueta de destino al enviar la transacción XRP para el intercambio",
|
||||||
"yat": "Yat",
|
"yat": "Yat",
|
||||||
"yat_address": "Dirección de Yat",
|
"yat_address": "Dirección de Yat",
|
||||||
"yat_alert_content": "Los usuarios de Cake Wallet ahora pueden enviar y recibir todas sus monedas favoritas con un nombre de usuario único basado en emoji.",
|
"yat_alert_content": "Los usuarios de Cake Wallet ahora pueden enviar y recibir todas sus monedas favoritas con un nombre de usuario único basado en emoji.",
|
||||||
"yat_alert_title": "Envíe y reciba criptomonedas más fácilmente con Yat",
|
"yat_alert_title": "Envía y recibe criptomonedas más fácilmente con Yat",
|
||||||
"yat_error": "Error de Yat",
|
"yat_error": "Error de Yat",
|
||||||
"yat_error_content": "No hay direcciones vinculadas con este Yat. Prueba con otro Yat",
|
"yat_error_content": "No hay direcciones vinculadas con este Yat. Prueba con otro Yat",
|
||||||
"yat_popup_content": "Ahora puede enviar y recibir criptografía en Cake Wallet con su Yat, un nombre de usuario corto basado en emoji. Administre Yats en cualquier momento en la pantalla de configuración",
|
"yat_popup_content": "Ahora puede enviar y recibir criptografía en Cake Wallet con su Yat, un nombre de usuario corto basado en emoji. Administra Yats en cualquier momento en la pantalla de configuración",
|
||||||
"yat_popup_title": "La dirección de su billetera se puede emojificar.",
|
"yat_popup_title": "La dirección de tu billetera se puede emojificar.",
|
||||||
"yesterday": "Ayer",
|
"yesterday": "Ayer",
|
||||||
"you_now_have_debit_card": "Ahora tiene una tarjeta de débito",
|
"you_now_have_debit_card": "Ahora tienes una tarjeta de débito",
|
||||||
"you_pay": "Tú pagas",
|
"you_pay": "Tú pagas",
|
||||||
"you_will_get": "Convertir a",
|
"you_will_get": "Convertir a",
|
||||||
"you_will_send": "Convertir de",
|
"you_will_send": "Convertir de",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -709,6 +709,7 @@
|
||||||
"silent_payments_always_scan": "Définir les paiements silencieux toujours à la scanne",
|
"silent_payments_always_scan": "Définir les paiements silencieux toujours à la scanne",
|
||||||
"silent_payments_disclaimer": "Les nouvelles adresses ne sont pas de nouvelles identités. Il s'agit d'une réutilisation d'une identité existante avec une étiquette différente.",
|
"silent_payments_disclaimer": "Les nouvelles adresses ne sont pas de nouvelles identités. Il s'agit d'une réutilisation d'une identité existante avec une étiquette différente.",
|
||||||
"silent_payments_display_card": "Afficher la carte de paiement silencieuse",
|
"silent_payments_display_card": "Afficher la carte de paiement silencieuse",
|
||||||
|
"silent_payments_register_key": "Enregistrez la touche Afficher pour une analyse plus rapide",
|
||||||
"silent_payments_scan_from_date": "Analyser à partir de la date",
|
"silent_payments_scan_from_date": "Analyser à partir de la date",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Veuillez saisir la hauteur du bloc que vous souhaitez commencer à scanner pour les paiements silencieux entrants, ou utilisez la date à la place. Vous pouvez choisir si le portefeuille continue de numériser chaque bloc ou ne vérifie que la hauteur spécifiée.",
|
"silent_payments_scan_from_date_or_blockheight": "Veuillez saisir la hauteur du bloc que vous souhaitez commencer à scanner pour les paiements silencieux entrants, ou utilisez la date à la place. Vous pouvez choisir si le portefeuille continue de numériser chaque bloc ou ne vérifie que la hauteur spécifiée.",
|
||||||
"silent_payments_scan_from_height": "Scan à partir de la hauteur du bloc",
|
"silent_payments_scan_from_height": "Scan à partir de la hauteur du bloc",
|
||||||
|
|
|
@ -711,6 +711,7 @@
|
||||||
"silent_payments_always_scan": "Saita biya na shiru koyaushe",
|
"silent_payments_always_scan": "Saita biya na shiru koyaushe",
|
||||||
"silent_payments_disclaimer": "Sabbin adiresoshin ba sabon tsari bane. Wannan shine sake amfani da asalin asalin tare da wata alama daban.",
|
"silent_payments_disclaimer": "Sabbin adiresoshin ba sabon tsari bane. Wannan shine sake amfani da asalin asalin tare da wata alama daban.",
|
||||||
"silent_payments_display_card": "Nuna katin silent",
|
"silent_payments_display_card": "Nuna katin silent",
|
||||||
|
"silent_payments_register_key": "Yi rijista mabuɗin don bincika sauri",
|
||||||
"silent_payments_scan_from_date": "Scan daga kwanan wata",
|
"silent_payments_scan_from_date": "Scan daga kwanan wata",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Da fatan za a shigar da toshe wurin da kake son fara bincika don biyan silins mai shigowa, ko, yi amfani da kwanan wata. Zaka iya zabar idan walat ɗin ya ci gaba da bincika kowane toshe, ko duba tsinkaye da aka ƙayyade.",
|
"silent_payments_scan_from_date_or_blockheight": "Da fatan za a shigar da toshe wurin da kake son fara bincika don biyan silins mai shigowa, ko, yi amfani da kwanan wata. Zaka iya zabar idan walat ɗin ya ci gaba da bincika kowane toshe, ko duba tsinkaye da aka ƙayyade.",
|
||||||
"silent_payments_scan_from_height": "Scan daga tsayin daka",
|
"silent_payments_scan_from_height": "Scan daga tsayin daka",
|
||||||
|
|
|
@ -711,6 +711,7 @@
|
||||||
"silent_payments_always_scan": "मूक भुगतान हमेशा स्कैनिंग सेट करें",
|
"silent_payments_always_scan": "मूक भुगतान हमेशा स्कैनिंग सेट करें",
|
||||||
"silent_payments_disclaimer": "नए पते नई पहचान नहीं हैं। यह एक अलग लेबल के साथ एक मौजूदा पहचान का पुन: उपयोग है।",
|
"silent_payments_disclaimer": "नए पते नई पहचान नहीं हैं। यह एक अलग लेबल के साथ एक मौजूदा पहचान का पुन: उपयोग है।",
|
||||||
"silent_payments_display_card": "मूक भुगतान कार्ड दिखाएं",
|
"silent_payments_display_card": "मूक भुगतान कार्ड दिखाएं",
|
||||||
|
"silent_payments_register_key": "तेजी से स्कैनिंग के लिए रजिस्टर व्यू कुंजी",
|
||||||
"silent_payments_scan_from_date": "तिथि से स्कैन करना",
|
"silent_payments_scan_from_date": "तिथि से स्कैन करना",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "कृपया उस ब्लॉक ऊंचाई दर्ज करें जिसे आप आने वाले मूक भुगतान के लिए स्कैन करना शुरू करना चाहते हैं, या, इसके बजाय तारीख का उपयोग करें। आप चुन सकते हैं कि क्या वॉलेट हर ब्लॉक को स्कैन करना जारी रखता है, या केवल निर्दिष्ट ऊंचाई की जांच करता है।",
|
"silent_payments_scan_from_date_or_blockheight": "कृपया उस ब्लॉक ऊंचाई दर्ज करें जिसे आप आने वाले मूक भुगतान के लिए स्कैन करना शुरू करना चाहते हैं, या, इसके बजाय तारीख का उपयोग करें। आप चुन सकते हैं कि क्या वॉलेट हर ब्लॉक को स्कैन करना जारी रखता है, या केवल निर्दिष्ट ऊंचाई की जांच करता है।",
|
||||||
"silent_payments_scan_from_height": "ब्लॉक ऊंचाई से स्कैन करें",
|
"silent_payments_scan_from_height": "ब्लॉक ऊंचाई से स्कैन करें",
|
||||||
|
|
|
@ -709,6 +709,7 @@
|
||||||
"silent_payments_always_scan": "Postavite tiho plaćanje uvijek skeniranje",
|
"silent_payments_always_scan": "Postavite tiho plaćanje uvijek skeniranje",
|
||||||
"silent_payments_disclaimer": "Nove adrese nisu novi identiteti. To je ponovna upotreba postojećeg identiteta s drugom oznakom.",
|
"silent_payments_disclaimer": "Nove adrese nisu novi identiteti. To je ponovna upotreba postojećeg identiteta s drugom oznakom.",
|
||||||
"silent_payments_display_card": "Prikaži karticu tihih plaćanja",
|
"silent_payments_display_card": "Prikaži karticu tihih plaćanja",
|
||||||
|
"silent_payments_register_key": "Registrirajte ključ za brže skeniranje",
|
||||||
"silent_payments_scan_from_date": "Skeniranje iz datuma",
|
"silent_payments_scan_from_date": "Skeniranje iz datuma",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Unesite visinu bloka koju želite započeti skeniranje za dolazna tiha plaćanja ili umjesto toga upotrijebite datum. Možete odabrati da li novčanik nastavlja skenirati svaki blok ili provjerava samo navedenu visinu.",
|
"silent_payments_scan_from_date_or_blockheight": "Unesite visinu bloka koju želite započeti skeniranje za dolazna tiha plaćanja ili umjesto toga upotrijebite datum. Možete odabrati da li novčanik nastavlja skenirati svaki blok ili provjerava samo navedenu visinu.",
|
||||||
"silent_payments_scan_from_height": "Skeniranje s visine bloka",
|
"silent_payments_scan_from_height": "Skeniranje s visine bloka",
|
||||||
|
|
|
@ -701,6 +701,7 @@
|
||||||
"silent_payments_always_scan": "Միացնել Լուռ Վճարումներ մշտական սկանավորումը",
|
"silent_payments_always_scan": "Միացնել Լուռ Վճարումներ մշտական սկանավորումը",
|
||||||
"silent_payments_disclaimer": "Նոր հասցեները նոր ինքնություն չեն։ Դա այլ պիտակով գոյություն ունեցող ինքնության վերագործածում է",
|
"silent_payments_disclaimer": "Նոր հասցեները նոր ինքնություն չեն։ Դա այլ պիտակով գոյություն ունեցող ինքնության վերագործածում է",
|
||||||
"silent_payments_display_card": "Ցուցադրել Լուռ Վճարումներ քարտը",
|
"silent_payments_display_card": "Ցուցադրել Լուռ Վճարումներ քարտը",
|
||||||
|
"silent_payments_register_key": "Գրանցեք Դիտել ստեղնը `ավելի արագ սկանավորման համար",
|
||||||
"silent_payments_scan_from_date": "Սկանավորել ամսաթվից",
|
"silent_payments_scan_from_date": "Սկանավորել ամսաթվից",
|
||||||
"silent_payments_scan_from_date_or_blockheight": "Խնդրում ենք մուտքագրել բլոկի բարձրությունը, որտեղից դուք ցանկանում եք սկսել սկանավորել մուտքային Լուռ Վճարումները կամ տեղափոխել ամսաթվի փոխարեն։ Դուք կարող եք ընտրել, արդյոք դրամապանակը շարունակելու է սկանավորել ամեն բլոկ կամ ստուգել միայն սահմանված բարձրությունը",
|
"silent_payments_scan_from_date_or_blockheight": "Խնդրում ենք մուտքագրել բլոկի բարձրությունը, որտեղից դուք ցանկանում եք սկսել սկանավորել մուտքային Լուռ Վճարումները կամ տեղափոխել ամսաթվի փոխարեն։ Դուք կարող եք ընտրել, արդյոք դրամապանակը շարունակելու է սկանավորել ամեն բլոկ կամ ստուգել միայն սահմանված բարձրությունը",
|
||||||
"silent_payments_scan_from_height": "Բլոկի բարձրությունից սկանավորել",
|
"silent_payments_scan_from_height": "Բլոկի բարձրությունից սկանավորել",
|
||||||
|
|
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