feat: pull changes from silent payments

This commit is contained in:
Rafael Saes 2023-11-30 07:01:58 -03:00
parent e092509264
commit 5c205e4cc8
87 changed files with 1750 additions and 847 deletions

View file

@ -4,20 +4,16 @@ import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) {
try {
return bitcoin.P2PKH(
data: PaymentData(output: script),
network: networkType)
.data
.address!;
return bitcoin.P2PKH(data: PaymentData(output: script), network: networkType).data.address!;
} catch (_) {}
try {
return bitcoin.P2WPKH(
data: PaymentData(output: script),
network: networkType)
.data
.address!;
} catch(_) {}
return bitcoin.P2WPKH(data: PaymentData(output: script), network: networkType).data.address!;
} catch (_) {}
try {
return bitcoin.P2TR(data: PaymentData(output: script), network: networkType).data.address!;
} catch (_) {}
return '';
}
}

View file

@ -1,26 +1,37 @@
import 'dart:convert';
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
class BitcoinAddressRecord {
BitcoinAddressRecord(this.address,
{required this.index, this.isHidden = false, bool isUsed = false})
: _isUsed = isUsed;
{required this.index,
this.isHidden = false,
bool isUsed = false,
this.silentAddressLabel,
this.silentPaymentTweak,
this.type})
: _isUsed = isUsed;
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(
decoded['address'] as String,
index: decoded['index'] as int,
isHidden: decoded['isHidden'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false);
return BitcoinAddressRecord(decoded['address'] as String,
index: decoded['index'] as int,
isHidden: decoded['isHidden'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false,
silentAddressLabel: decoded['silentAddressLabel'] as String?,
silentPaymentTweak: decoded['silentPaymentTweak'] as String?,
type: decoded['type'] != null && decoded['type'] != ''
? AddressType.values.firstWhere((type) => type.toString() == decoded['type'] as String)
: null);
}
@override
bool operator ==(Object o) =>
o is BitcoinAddressRecord && address == o.address;
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
final String address;
final bool isHidden;
final String? silentAddressLabel;
final String? silentPaymentTweak;
final int index;
bool get isUsed => _isUsed;
@ -31,10 +42,15 @@ class BitcoinAddressRecord {
void setAsUsed() => _isUsed = true;
String toJSON() =>
json.encode({
AddressType? type;
String toJSON() => json.encode({
'address': address,
'index': index,
'isHidden': isHidden,
'isUsed': isUsed});
'isUsed': isUsed,
'silentAddressLabel': silentAddressLabel,
'silentPaymentTweak': silentPaymentTweak,
'type': type?.toString() ?? '',
});
}

View file

@ -2,14 +2,19 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_core/unspent_transaction_output.dart';
class BitcoinUnspent extends Unspent {
BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout,
{this.silentPaymentTweak, bool? isTaproot})
: bitcoinAddressRecord = addressRecord,
isTaproot = isTaproot ?? false,
super(addressRecord.address, hash, value, vout, null);
factory BitcoinUnspent.fromJSON(
BitcoinAddressRecord address, Map<String, dynamic> json) =>
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
json['tx_pos'] as int);
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
BitcoinUnspent(
address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int,
silentPaymentTweak: json['silent_payment_tweak'] as String?,
isTaproot: json['is_taproot'] as bool?);
final BitcoinAddressRecord bitcoinAddressRecord;
String? silentPaymentTweak;
bool isTaproot = false;
}

View file

@ -22,53 +22,58 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
bitcoin.NetworkType? networkType,
required Uint8List seedBytes,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
int initialChangeAddressIndex = 0,
bitcoin.SilentPaymentReceiver? silentAddress})
: super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btc) {
walletAddresses = BitcoinWalletAddresses(
walletInfo,
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: networkType ?? bitcoin.bitcoin,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btc,
) {
walletAddresses = BitcoinWalletAddresses(walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: hd,
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
.derivePath("m/0'/1"),
networkType: networkType);
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
networkType: networkType ?? bitcoin.bitcoin,
silentAddress: silentAddress);
}
static Future<BitcoinWallet> create({
required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0
}) async {
static Future<BitcoinWallet> create(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
bitcoin.NetworkType? networkType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0}) async {
return BitcoinWallet(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: networkType,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await mnemonicToSeedBytes(mnemonic),
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex);
initialChangeAddressIndex: initialChangeAddressIndex,
silentAddress: await bitcoin.SilentPaymentReceiver.fromMnemonic(mnemonic,
hrp: networkType == bitcoin.bitcoin ? 'sp' : 'tsp'));
}
static Future<BitcoinWallet> open({
@ -77,16 +82,19 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password);
return BitcoinWallet(
mnemonic: snp.mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
networkType: snp.networkType,
initialAddresses: snp.addresses,
initialBalance: snp.balance,
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
initialChangeAddressIndex: snp.changeAddressIndex,
silentAddress: await bitcoin.SilentPaymentReceiver.fromMnemonic(snp.mnemonic,
hrp: snp.networkType == bitcoin.bitcoin ? 'sp' : 'tsp'));
}
}
}

View file

@ -18,7 +18,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
int initialChangeAddressIndex = 0,
bitcoin.SilentPaymentReceiver? silentAddress})
: super(walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
@ -26,9 +27,18 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
networkType: networkType,
silentAddress: silentAddress);
@override
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
String getAddress(
{required int index, required bitcoin.HDWallet hd, bitcoin.AddressType? addressType}) {
if (addressType == bitcoin.AddressType.p2pkh)
return generateP2PKHAddress(hd: hd, index: index, networkType: networkType);
if (addressType == bitcoin.AddressType.p2tr)
return generateP2TRAddress(hd: hd, index: index, networkType: networkType);
return generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
}
}

View file

@ -11,11 +11,10 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:collection/collection.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
class BitcoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials> {
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource;
@ -25,12 +24,14 @@ class BitcoinWalletService extends WalletService<
WalletType getType() => WalletType.bitcoin;
@override
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final wallet = await BitcoinWalletBase.create(
mnemonic: await generateMnemonic(),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);
mnemonic: await generateMnemonic(),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
networkType: isTestnet == true ? bitcoin.testnet : bitcoin.bitcoin,
);
await wallet.save();
await wallet.init();
return wallet;
@ -42,33 +43,36 @@ class BitcoinWalletService extends WalletService<
@override
Future<BitcoinWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(name, getType()))!;
final walletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
final wallet = await BitcoinWalletBase.open(
password: password, name: name, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
);
await wallet.init();
return wallet;
}
@override
Future<void> remove(String wallet) async {
File(await pathForWalletDir(name: wallet, type: getType()))
.delete(recursive: true);
final walletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
final walletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
await walletInfoSource.delete(walletInfo.key);
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWalletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWallet = await BitcoinWalletBase.open(
password: password,
name: currentName,
walletInfo: currentWalletInfo,
unspentCoinsInfo: unspentCoinsInfoSource);
password: password,
name: currentName,
walletInfo: currentWalletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
);
await currentWallet.renameWalletFiles(newName);
@ -80,24 +84,23 @@ class BitcoinWalletService extends WalletService<
}
@override
Future<BitcoinWallet> restoreFromKeys(
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async =>
throw UnimplementedError();
@override
Future<BitcoinWallet> restoreFromSeed(
BitcoinRestoreWalletFromSeedCredentials credentials) async {
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}
final wallet = await BitcoinWalletBase.create(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource);
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
);
await wallet.save();
await wallet.init();
return wallet;
}
}
}

View file

@ -8,6 +8,7 @@ import 'package:cw_bitcoin/script_hash.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
String jsonrpcparams(List<Object> params) {
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
@ -22,10 +23,7 @@ String jsonrpc(
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
class SocketTask {
SocketTask({
required this.isSubscription,
this.completer,
this.subject});
SocketTask({required this.isSubscription, this.completer, this.subject});
final Completer<dynamic>? completer;
final BehaviorSubject<dynamic>? subject;
@ -51,8 +49,12 @@ class ElectrumClient {
Timer? _aliveTimer;
String unterminatedString;
Future<void> connectToUri(Uri uri) async =>
Uri? uri;
Future<void> connectToUri(Uri uri) async {
this.uri = uri;
await connect(host: uri.host, port: uri.port);
}
Future<void> connect({required String host, required int port}) async {
try {
@ -104,21 +106,20 @@ class ElectrumClient {
}
if (isJSONStringCorrect(unterminatedString)) {
final response =
json.decode(unterminatedString) as Map<String, dynamic>;
final response = json.decode(unterminatedString) as Map<String, dynamic>;
_handleResponse(response);
unterminatedString = '';
}
} on TypeError catch (e) {
if (!e.toString().contains('Map<String, Object>') && !e.toString().contains('Map<String, dynamic>')) {
if (!e.toString().contains('Map<String, Object>') &&
!e.toString().contains('Map<String, dynamic>')) {
return;
}
unterminatedString += message;
if (isJSONStringCorrect(unterminatedString)) {
final response =
json.decode(unterminatedString) as Map<String, dynamic>;
final response = json.decode(unterminatedString) as Map<String, dynamic>;
_handleResponse(response);
// unterminatedString = null;
unterminatedString = '';
@ -142,8 +143,7 @@ class ElectrumClient {
}
}
Future<List<String>> version() =>
call(method: 'server.version').then((dynamic result) {
Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
if (result is List) {
return result.map((dynamic val) => val.toString()).toList();
}
@ -180,9 +180,8 @@ class ElectrumClient {
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
String address, NetworkType networkType) =>
call(
method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address, networkType: networkType)])
.then((dynamic result) {
method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address, networkType: networkType)]).then((dynamic result) {
if (result is List) {
return result.map((dynamic val) {
if (val is Map<String, dynamic>) {
@ -229,19 +228,25 @@ class ElectrumClient {
return [];
});
Future<Map<String, dynamic>> getTransactionRaw(
{required String hash}) async =>
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
Future<dynamic> getTransactionRaw(
{required String hash, required NetworkType networkType}) async =>
callWithTimeout(
method: 'blockchain.transaction.get',
params: networkType.bech32 == bitcoin.bech32 ? [hash, true] : [hash],
timeout: 10000)
.then((dynamic result) {
if (result is Map<String, dynamic>) {
return result;
}
if (networkType.bech32 == testnet.bech32 && result is String) {
return result;
}
return <String, dynamic>{};
});
Future<String> getTransactionHex(
{required String hash}) async =>
Future<String> getTransactionHex({required String hash}) async =>
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
.then((dynamic result) {
if (result is String) {
@ -251,30 +256,41 @@ class ElectrumClient {
return '';
});
Future<String> broadcastTransaction(
{required String transactionRaw}) async =>
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
.then((dynamic result) {
if (result is String) {
return result;
}
Future<String> broadcastTransaction({required String transactionRaw}) async {
return http
.post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'),
headers: <String, String>{'Content-Type': 'application/json; charset=utf-8'},
body: transactionRaw)
.then((http.Response response) {
print(response.body);
if (response.statusCode == 200) {
return response.body;
}
return '';
});
return '';
});
return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
.then((dynamic result) {
if (result is String) {
return result;
}
Future<Map<String, dynamic>> getMerkle(
{required String hash, required int height}) async =>
await call(
method: 'blockchain.transaction.get_merkle',
params: [hash, height]) as Map<String, dynamic>;
return '';
});
}
Future<Map<String, dynamic>> getHeader({required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height])
Future<String> getTxidFromPos({required int height, required int pos}) async =>
await call(method: 'blockchain.transaction.id_from_pos', params: [height, pos]) as String;
Future<Map<String, dynamic>> getMerkle({required String hash, required int height}) async =>
await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
as Map<String, dynamic>;
Future<Map<String, dynamic>> getHeader({required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
Future<double> estimatefee({required int p}) =>
call(method: 'blockchain.estimatefee', params: [p])
.then((dynamic result) {
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
if (result is double) {
return result;
}
@ -319,15 +335,9 @@ class ElectrumClient {
final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 5);
final bottomDoubleString = await estimatefee(p: 100);
final top =
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
.round();
final middle =
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
.round();
final bottom =
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
.round();
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
return [bottom, middle, top];
} catch (_) {
@ -335,6 +345,27 @@ class ElectrumClient {
}
}
// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe
// example response:
// {
// "height": 520481,
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
// }
Future<int?> getCurrentBlockChainTip() =>
call(method: 'blockchain.headers.subscribe').then((result) {
if (result is Map<String, dynamic>) {
return result["height"] as int;
}
return null;
});
BehaviorSubject<Object>? chainTipUpdate() {
_id += 1;
return subscribe<Object>(
id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe');
}
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
_id += 1;
return subscribe<Object>(
@ -344,16 +375,14 @@ class ElectrumClient {
}
BehaviorSubject<T>? subscribe<T>(
{required String id,
required String method,
List<Object> params = const []}) {
{required String id, required String method, List<Object> params = const []}) {
try {
final subscription = BehaviorSubject<T>();
_regisrySubscription(id, subscription);
socket!.write(jsonrpc(method: method, id: _id, params: params));
return subscription;
} catch(e) {
} catch (e) {
print(e.toString());
return null;
}
@ -370,9 +399,7 @@ class ElectrumClient {
}
Future<dynamic> callWithTimeout(
{required String method,
List<Object> params = const [],
int timeout = 4000}) async {
{required String method, List<Object> params = const [], int timeout = 4000}) async {
try {
final completer = Completer<dynamic>();
_id += 1;
@ -386,7 +413,7 @@ class ElectrumClient {
});
return completer.future;
} catch(e) {
} catch (e) {
print(e.toString());
}
}
@ -397,8 +424,8 @@ class ElectrumClient {
onConnectionStatusChange = null;
}
void _registryTask(int id, Completer<dynamic> completer) => _tasks[id.toString()] =
SocketTask(completer: completer, isSubscription: false);
void _registryTask(int id, Completer<dynamic> completer) =>
_tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false);
void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
@ -419,8 +446,7 @@ class ElectrumClient {
}
}
void _methodHandler(
{required String method, required Map<String, dynamic> request}) {
void _methodHandler({required String method, required Map<String, dynamic> request}) {
switch (method) {
case 'blockchain.scripthash.subscribe':
final params = request['params'] as List<dynamic>;
@ -451,8 +477,8 @@ class ElectrumClient {
_methodHandler(method: method, request: response);
return;
}
if (id != null){
if (id != null) {
_finish(id, result);
}
}

View file

@ -10,9 +10,7 @@ import 'package:cw_core/wallet_type.dart';
class ElectrumTransactionBundle {
ElectrumTransactionBundle(this.originalTransaction,
{required this.ins,
required this.confirmations,
this.time});
{required this.ins, required this.confirmations, this.time});
final bitcoin.Transaction originalTransaction;
final List<bitcoin.Transaction> ins;
final int? time;
@ -39,8 +37,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.confirmations = confirmations;
}
factory ElectrumTransactionInfo.fromElectrumVerbose(
Map<String, Object> obj, WalletType type,
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
{required List<BitcoinAddressRecord> addresses, required int height}) {
final addressesSet = addresses.map((addr) => addr.address).toSet();
final id = obj['txid'] as String;
@ -58,10 +55,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
for (dynamic vin in vins) {
final vout = vin['vout'] as int;
final out = vin['tx']['vout'][vout] as Map;
final outAddresses =
(out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
inputsAmount +=
stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
final outAddresses = (out['scriptPubKey']['addresses'] as List<Object>?)?.toSet();
inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double? ?? 0).toString());
if (outAddresses?.intersection(addressesSet).isNotEmpty ?? false) {
direction = TransactionDirection.outgoing;
@ -69,11 +64,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
}
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 value = stringDoubleToBitcoinAmount(
(out['value'] as double? ?? 0.0).toString());
final value = stringDoubleToBitcoinAmount((out['value'] as double? ?? 0.0).toString());
totalOutAmount += value;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
@ -96,14 +89,11 @@ class ElectrumTransactionInfo extends TransactionInfo {
}
factory ElectrumTransactionInfo.fromElectrumBundle(
ElectrumTransactionBundle bundle,
WalletType type,
bitcoin.NetworkType networkType,
{required Set<String> addresses,
required int height}) {
ElectrumTransactionBundle bundle, WalletType type, bitcoin.NetworkType networkType,
{required Set<String> addresses, required int height}) {
final date = bundle.time != null
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
: DateTime.now();
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
: DateTime.now();
var direction = TransactionDirection.incoming;
var amount = 0;
var inputAmount = 0;
@ -152,8 +142,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
if (addresses != null) {
tx.outs.forEach((out) {
try {
final p2pkh = bitcoin.P2PKH(
data: PaymentData(output: out.script), network: bitcoin.bitcoin);
final p2pkh =
bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin);
exist = addresses.contains(p2pkh.data.address);
if (exist) {
@ -163,9 +153,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
});
}
final date = timestamp != null
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
: DateTime.now();
final date =
timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now();
return ElectrumTransactionInfo(type,
id: tx.getId(),
@ -178,8 +167,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
confirmations: confirmations);
}
factory ElectrumTransactionInfo.fromJson(
Map<String, dynamic> data, WalletType type) {
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
return ElectrumTransactionInfo(type,
id: data['id'] as String,
height: data['height'] as int,

File diff suppressed because it is too large Load diff

View file

@ -10,31 +10,33 @@ import 'package:mobx/mobx.dart';
part 'electrum_wallet_addresses.g.dart';
class ElectrumWalletAddresses = ElectrumWalletAddressesBase
with _$ElectrumWalletAddresses;
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
ElectrumWalletAddressesBase(WalletInfo walletInfo,
{required this.mainHd,
required this.sideHd,
required this.electrumClient,
required this.networkType,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: addresses = ObservableList<BitcoinAddressRecord>.of(
(initialAddresses ?? []).toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
(initialAddresses ?? [])
ElectrumWalletAddressesBase(
WalletInfo walletInfo, {
required this.mainHd,
required this.sideHd,
required this.electrumClient,
required this.networkType,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0,
bitcoin.SilentPaymentReceiver? silentAddress,
}) : addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
silentAddress = silentAddress,
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
.toSet()),
changeAddresses = ObservableList<BitcoinAddressRecord>.of(
(initialAddresses ?? [])
.toSet()),
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
.toSet()),
.toSet()),
silentAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => addressRecord.silentAddressLabel != null)
.toSet()),
currentReceiveAddressIndex = initialRegularAddressIndex,
currentChangeAddressIndex = initialChangeAddressIndex,
super(walletInfo);
super(walletInfo);
static const defaultReceiveAddressesCount = 22;
static const defaultChangeAddressesCount = 17;
@ -45,14 +47,26 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final ObservableList<BitcoinAddressRecord> addresses;
final ObservableList<BitcoinAddressRecord> receiveAddresses;
final ObservableList<BitcoinAddressRecord> changeAddresses;
final ObservableList<BitcoinAddressRecord> silentAddresses;
final ElectrumClient electrumClient;
final bitcoin.NetworkType networkType;
final bitcoin.HDWallet mainHd;
final bitcoin.HDWallet sideHd;
@override
// TODO: labels -> disable edit on receive page
final bitcoin.SilentPaymentReceiver? silentAddress;
@observable
// ignore: prefer_final_fields
dynamic _addressPageType = bitcoin.AddressType.p2wpkh;
@computed
String get address {
dynamic get addressPageType => _addressPageType;
@observable
String? activeAddress;
@computed
String get receiveAddress {
if (receiveAddresses.isEmpty) {
final address = generateNewAddress().address;
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(address) : address;
@ -63,28 +77,54 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
@override
set address(String addr) => null;
@computed
String get address {
if (addressPageType == bitcoin.AddressType.p2sp) {
return silentAddress!.toString();
}
if (activeAddress != null) {
return activeAddress!;
}
if (receiveAddresses.isEmpty) {
return generateNewAddress().address;
}
try {
receiveAddresses.forEach(
(element) => print('element: ${element.address} ${element.type} $addressPageType'));
return receiveAddresses
.firstWhere((address) => addressPageType == bitcoin.AddressType.p2wpkh
? address.type == null || address.type == addressPageType
: address.type == addressPageType)
.address;
} catch (_) {}
return receiveAddresses.first.address;
}
@override
set address(String addr) => activeAddress = addr;
int currentReceiveAddressIndex;
int currentChangeAddressIndex;
@computed
int get totalCountOfReceiveAddresses =>
addresses.fold(0, (acc, addressRecord) {
if (!addressRecord.isHidden) {
return acc + 1;
}
return acc;
});
int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {
if (!addressRecord.isHidden) {
return acc + 1;
}
return acc;
});
@computed
int get totalCountOfChangeAddresses =>
addresses.fold(0, (acc, addressRecord) {
if (addressRecord.isHidden) {
return acc + 1;
}
return acc;
});
int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) {
if (addressRecord.isHidden) {
return acc + 1;
}
return acc;
});
Future<void> discoverAddresses() async {
await _discoverAddresses(mainHd, false);
@ -114,12 +154,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (changeAddresses.isEmpty) {
final newAddresses = await _createNewAddresses(gap,
hd: sideHd,
startIndex: totalCountOfChangeAddresses > 0
? totalCountOfChangeAddresses - 1
: 0,
isHidden: true);
_addAddresses(newAddresses);
hd: sideHd,
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
isHidden: true);
addAddresses(newAddresses);
}
if (currentChangeAddressIndex >= changeAddresses.length) {
@ -132,19 +170,44 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return address;
}
@action
BitcoinAddressRecord generateNewAddress(
{bitcoin.HDWallet? hd, bool isHidden = false}) {
currentReceiveAddressIndex += 1;
{bitcoin.HDWallet? hd, bool isHidden = false, String? label}) {
if (label != null && silentAddress != null) {
final address = BitcoinAddressRecord(
bitcoin.SilentPaymentAddress.createLabeledSilentPaymentAddress(
silentAddress!.scanPubkey,
silentAddress!.spendPubkey,
'0000000000000000000000000000000000000000000000000000000000000002'.fromHex,
hrp: silentAddress!.hrp,
version: silentAddress!.version)
.toString(),
index: currentReceiveAddressIndex,
isHidden: isHidden,
silentAddressLabel: label);
silentAddresses.add(address);
return address;
}
// FIX-ME: Check logic for whichi HD should be used here ???
final address = BitcoinAddressRecord(
getAddress(index: currentReceiveAddressIndex, hd: hd ?? sideHd),
getAddress(
index: currentReceiveAddressIndex,
hd: hd ?? sideHd,
addressType: addressPageType as bitcoin.AddressType,
),
index: currentReceiveAddressIndex,
isHidden: isHidden);
addresses.add(address);
return address;
currentReceiveAddressIndex += 1;
}
String getAddress({required int index, required bitcoin.HDWallet hd}) => '';
String getAddress(
{required int index, required bitcoin.HDWallet hd, bitcoin.AddressType? addressType}) =>
'';
@override
Future<void> updateAddressesInBox() async {
@ -160,38 +223,37 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
void updateReceiveAddresses() {
receiveAddresses.removeRange(0, receiveAddresses.length);
final newAdresses = addresses
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
final newAdresses =
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
receiveAddresses.addAll(newAdresses);
}
@action
void updateChangeAddresses() {
changeAddresses.removeRange(0, changeAddresses.length);
final newAdresses = addresses
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
final newAdresses =
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
changeAddresses.addAll(newAdresses);
}
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden) async {
@action
Future<void> _discoverAddresses(bitcoin.HDWallet hd, bool isHidden,
{bitcoin.AddressType? addressType}) async {
var hasAddrUse = true;
List<BitcoinAddressRecord> addrs;
if (addresses.isNotEmpty) {
addrs = addresses
.where((addr) => addr.isHidden == isHidden)
.toList();
if (addresses.where((addr) => addr.type == addressPageType).isNotEmpty) {
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
} else {
addrs = await _createNewAddresses(
isHidden
? defaultChangeAddressesCount
: defaultReceiveAddressesCount,
isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
startIndex: 0,
hd: hd,
isHidden: isHidden);
isHidden: isHidden,
addressType: addressType);
}
while(hasAddrUse) {
while (hasAddrUse) {
final addr = addrs.last.address;
hasAddrUse = await _hasAddressUsed(addr);
@ -201,16 +263,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final start = addrs.length;
final count = start + gap;
final batch = await _createNewAddresses(
count,
startIndex: start,
hd: hd,
isHidden: isHidden);
final batch = await _createNewAddresses(count,
startIndex: start, hd: hd, isHidden: isHidden, addressType: addressType);
addrs.addAll(batch);
}
if (addresses.length < addrs.length) {
_addAddresses(addrs);
if (addresses.length < addrs.length || addressPageType != null) {
addAddresses(addrs);
}
}
@ -229,45 +288,41 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
final newAddresses = await _createNewAddresses(
addressesCount,
startIndex: countOfReceiveAddresses,
hd: mainHd,
isHidden: false);
final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false);
addresses.addAll(newAddresses);
}
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
final newAddresses = await _createNewAddresses(
addressesCount,
startIndex: countOfHiddenAddresses,
hd: sideHd,
isHidden: true);
final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true);
addresses.addAll(newAddresses);
}
}
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
{required bitcoin.HDWallet hd, int startIndex = 0, bool isHidden = false}) async {
{required bitcoin.HDWallet hd,
int startIndex = 0,
bool isHidden = false,
bitcoin.AddressType? addressType}) async {
final list = <BitcoinAddressRecord>[];
for (var i = startIndex; i < count + startIndex; i++) {
final address = BitcoinAddressRecord(
getAddress(index: i, hd: hd),
index: i,
isHidden: isHidden);
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd, addressType: addressType),
index: i, isHidden: isHidden, type: addressType);
list.add(address);
}
return list;
}
void _addAddresses(Iterable<BitcoinAddressRecord> addresses) {
@action
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
final addressesSet = this.addresses.toSet();
addressesSet.addAll(addresses);
this.addresses.removeRange(0, this.addresses.length);
this.addresses.addAll(addressesSet);
this.addresses.addAll(addresses);
}
Future<bool> _hasAddressUsed(String address) async {
@ -275,4 +330,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final transactionHistory = await electrumClient.getHistory(sh);
return transactionHistory.isNotEmpty;
}
}
@override
@action
Future<void> setAddressType(dynamic type) async {
_addressPageType = type as bitcoin.AddressType;
if (addressPageType != bitcoin.AddressType.p2sp) {
await _discoverAddresses(mainHd, false, addressType: addressPageType as bitcoin.AddressType);
updateReceiveAddresses();
}
}
}

View file

@ -4,17 +4,20 @@ import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/file.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
class ElectrumWallletSnapshot {
ElectrumWallletSnapshot({
class ElectrumWalletSnapshot {
ElectrumWalletSnapshot({
required this.name,
required this.type,
required this.password,
required this.mnemonic,
required this.addresses,
required this.balance,
required this.networkType,
required this.regularAddressIndex,
required this.changeAddressIndex});
required this.changeAddressIndex,
});
final String name;
final String password;
@ -23,10 +26,11 @@ class ElectrumWallletSnapshot {
String mnemonic;
List<BitcoinAddressRecord> addresses;
ElectrumBalance balance;
bitcoin.NetworkType networkType;
int regularAddressIndex;
int changeAddressIndex;
static Future<ElectrumWallletSnapshot> load(String name, WalletType type, String password) async {
static Future<ElectrumWalletSnapshot> load(String name, WalletType type, String password) async {
final path = await pathForWallet(name: name, type: type);
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
@ -38,6 +42,7 @@ class ElectrumWallletSnapshot {
.toList();
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
final networkType = data['network_type'] == 'testnet' ? bitcoin.testnet : bitcoin.bitcoin;
var regularAddressIndex = 0;
var changeAddressIndex = 0;
@ -46,14 +51,15 @@ class ElectrumWallletSnapshot {
changeAddressIndex = int.parse(data['change_address_index'] as String? ?? '0');
} catch (_) {}
return ElectrumWallletSnapshot(
name: name,
type: type,
password: password,
mnemonic: mnemonic,
addresses: addresses,
balance: balance,
regularAddressIndex: regularAddressIndex,
changeAddressIndex: changeAddressIndex);
return ElectrumWalletSnapshot(
name: name,
type: type,
password: password,
mnemonic: mnemonic,
addresses: addresses,
balance: balance,
networkType: networkType,
regularAddressIndex: regularAddressIndex,
changeAddressIndex: changeAddressIndex);
}
}
}

View file

@ -1,9 +1,15 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
final litecoinNetwork = NetworkType(
messagePrefix: '\x19Litecoin Signed Message:\n',
bech32: 'ltc',
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0);
messagePrefix: '\x19Litecoin Signed Message:\n',
bech32: 'ltc',
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0,
p2pkhPrefix: bitcoin.p2pkhPrefix,
network: bitcoin.network,
p2shPrefix: bitcoin.p2shPrefix,
extendPublic: bitcoin.extendPublic,
extendPrivate: bitcoin.extendPrivate,
);

View file

@ -81,7 +81,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
final snp = await ElectrumWallletSnapshot.load (name, walletInfo.type, password);
final snp = await ElectrumWalletSnapshot.load (name, walletInfo.type, password);
return LitecoinWallet(
mnemonic: snp.mnemonic,
password: password,

View file

@ -9,31 +9,29 @@ import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart';
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
with _$LitecoinWalletAddresses;
class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
with Store {
LitecoinWalletAddressesBase(
WalletInfo walletInfo,
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
LitecoinWalletAddressesBase(WalletInfo walletInfo,
{required bitcoin.HDWallet mainHd,
required bitcoin.HDWallet sideHd,
required bitcoin.NetworkType networkType,
required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(
walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
required bitcoin.HDWallet sideHd,
required bitcoin.NetworkType networkType,
required ElectrumClient electrumClient,
List<BitcoinAddressRecord>? initialAddresses,
int initialRegularAddressIndex = 0,
int initialChangeAddressIndex = 0})
: super(walletInfo,
initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
mainHd: mainHd,
sideHd: sideHd,
electrumClient: electrumClient,
networkType: networkType);
@override
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
String getAddress(
{required int index, required bitcoin.HDWallet hd, bitcoin.AddressType? addressType}) =>
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
}
}

View file

@ -25,7 +25,7 @@ class LitecoinWalletService extends WalletService<
WalletType getType() => WalletType.litecoin;
@override
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final wallet = await LitecoinWalletBase.create(
mnemonic: await generateMnemonic(),
password: credentials.password!,

View file

@ -9,9 +9,7 @@ import 'package:cw_core/wallet_type.dart';
class PendingBitcoinTransaction with PendingTransaction {
PendingBitcoinTransaction(this._tx, this.type,
{required this.electrumClient,
required this.amount,
required this.fee})
{required this.electrumClient, required this.amount, required this.fee})
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type;
@ -37,7 +35,7 @@ class PendingBitcoinTransaction with PendingTransaction {
@override
Future<void> commit() async {
final result =
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
await electrumClient.broadcastTransaction(transactionRaw: _tx.txHex ?? _tx.toHex());
if (result.isEmpty) {
throw BitcoinCommitTransactionException();
@ -46,8 +44,7 @@ class PendingBitcoinTransaction with PendingTransaction {
_listeners?.forEach((listener) => listener(transactionInfo()));
}
void addListener(
void Function(ElectrumTransactionInfo transaction) listener) =>
void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
_listeners.add(listener);
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,

View file

@ -1,9 +1,29 @@
import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:crypto/crypto.dart';
String scriptHash(String address, {required bitcoin.NetworkType networkType}) {
final outputScript =
bitcoin.Address.addressToOutputScript(address, networkType);
try {
final outputScript = bitcoin.Address.addressToOutputScript(address, networkType);
final parts = sha256.convert(outputScript).toString().split('');
var res = '';
for (var i = parts.length - 1; i >= 0; i--) {
final char = parts[i];
i--;
final nextChar = parts[i];
res += nextChar;
res += char;
}
return res;
} catch (e) {}
return "";
}
String scriptHashFromScript(Uint8List outputScript, {required bitcoin.NetworkType networkType}) {
final parts = sha256.convert(outputScript).toString().split('');
var res = '';

View file

@ -4,15 +4,11 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:hex/hex.dart';
bitcoin.PaymentData generatePaymentData(
{required bitcoin.HDWallet hd, required int index}) =>
PaymentData(
pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, required int index}) =>
PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
bitcoin.ECPair generateKeyPair(
{required bitcoin.HDWallet hd,
required int index,
required bitcoin.NetworkType network}) =>
{required bitcoin.HDWallet hd, required int index, required bitcoin.NetworkType network}) =>
bitcoin.ECPair.fromWIF(hd.derive(index).wif!, network: network);
String generateP2WPKHAddress(
@ -28,28 +24,19 @@ String generateP2WPKHAddress(
.data
.address!;
String generateP2WPKHAddressByPath(
{required bitcoin.HDWallet hd,
required String path,
required bitcoin.NetworkType networkType}) =>
bitcoin
.P2WPKH(
data: PaymentData(
pubkey:
Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey!))),
network: networkType)
.data
.address!;
String generateP2PKHAddress(
{required bitcoin.HDWallet hd,
required int index,
required bitcoin.NetworkType networkType}) =>
bitcoin
.P2PKH(
data: PaymentData(
pubkey:
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))),
data: PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!))),
network: networkType)
.data
.address!;
String generateP2TRAddress(
{required bitcoin.HDWallet hd,
required int index,
required bitcoin.NetworkType networkType}) =>
bitcoin.P2trAddress(program: hd.derive(index).pubKey!).toAddress(networkType);

View file

@ -21,18 +21,18 @@ packages:
dependency: transitive
description:
name: args
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.2"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039
sha256: "21afe4333076c02877d14f4a89df111e658a6d466cbfc802eb705eb91bd5adfd"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.5.0"
async:
dependency: transitive
description:
@ -45,9 +45,9 @@ packages:
dependency: transitive
description:
path: "."
ref: "cake-0.2.2"
resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192"
url: "https://github.com/cake-tech/bech32.git"
ref: bech32m
resolved-ref: "55d966503310fc46bf349e2f56fe1bb513ed429b"
url: "https://github.com/saltrafael/bech32.git"
source: git
version: "0.2.2"
bip32:
@ -75,12 +75,20 @@ packages:
url: "https://github.com/cake-tech/bitbox-flutter.git"
source: git
version: "1.0.1"
bitcoin_base:
dependency: transitive
description:
name: bitcoin_base
sha256: f744eca882f501108639946e1172ab0b2e5553169dffc973cd0bfa78f25986d4
url: "https://pub.dev"
source: hosted
version: "0.5.0"
bitcoin_flutter:
dependency: "direct main"
description:
path: "."
ref: cake-update-v3
resolved-ref: df9204144011ed9419eff7d9ef3143102a40252d
ref: silent-payments
resolved-ref: "58d78564cf889af7bfea42eee48ebef467619b97"
url: "https://github.com/cake-tech/bitcoin_flutter.git"
source: git
version: "2.0.2"
@ -104,10 +112,10 @@ packages:
dependency: transitive
description:
name: build
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.4.1"
build_config:
dependency: transitive
description:
@ -120,10 +128,10 @@ packages:
dependency: transitive
description:
name: build_daemon
sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf"
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "4.0.1"
build_resolvers:
dependency: "direct dev"
description:
@ -136,18 +144,18 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
version: "2.4.7"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
url: "https://pub.dev"
source: hosted
version: "7.2.7"
version: "7.2.10"
built_collection:
dependency: transitive
description:
@ -160,10 +168,10 @@ packages:
dependency: transitive
description:
name: built_value
sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725"
sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2"
url: "https://pub.dev"
source: hosted
version: "8.4.3"
version: "8.8.0"
characters:
dependency: transitive
description:
@ -176,10 +184,10 @@ packages:
dependency: transitive
description:
name: checked_yaml
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.2"
version: "2.0.3"
clock:
dependency: transitive
description:
@ -192,10 +200,10 @@ packages:
dependency: transitive
description:
name: code_builder
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f
url: "https://pub.dev"
source: hosted
version: "4.4.0"
version: "4.8.0"
collection:
dependency: transitive
description:
@ -216,18 +224,18 @@ packages:
dependency: transitive
description:
name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.3"
cryptography:
dependency: "direct main"
description:
name: cryptography
sha256: e0e37f79665cd5c86e8897f9abe1accfe813c0cc5299dab22256e22fddc1fef8
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
url: "https://pub.dev"
source: hosted
version: "2.0.5"
version: "2.5.0"
cw_core:
dependency: "direct main"
description:
@ -243,14 +251,23 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.4"
elliptic:
dependency: transitive
description:
path: "."
ref: silent-payments
resolved-ref: "09343cf1bc03e74715413972dbec62a2505f5011"
url: "https://github.com/cake-tech/dart-elliptic"
source: git
version: "0.3.10"
encrypt:
dependency: "direct main"
description:
name: encrypt
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
url: "https://pub.dev"
source: hosted
version: "5.0.1"
version: "5.0.3"
fake_async:
dependency: transitive
description:
@ -263,10 +280,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.1.0"
file:
dependency: transitive
description:
@ -292,10 +309,10 @@ packages:
dependency: "direct main"
description:
name: flutter_mobx
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
sha256: d1d379561fe84675cc099bc42e99456f73a77fff475d0e2e9bda271751a11a91
url: "https://pub.dev"
source: hosted
version: "2.0.6+5"
version: "2.2.0"
flutter_test:
dependency: "direct dev"
description: flutter
@ -313,18 +330,18 @@ packages:
dependency: transitive
description:
name: glob
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
graphs:
dependency: transitive
description:
name: graphs
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.1"
hex:
dependency: transitive
description:
@ -401,18 +418,18 @@ packages:
dependency: transitive
description:
name: json_annotation
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev"
source: hosted
version: "4.8.0"
version: "4.8.1"
logging:
dependency: transitive
description:
name: logging
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
version: "1.2.0"
matcher:
dependency: transitive
description:
@ -449,18 +466,26 @@ packages:
dependency: "direct main"
description:
name: mobx
sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a
sha256: "42ae7277ec5c36fa5ce02aa14551065babce3c38a35947330144ff47bc775c75"
url: "https://pub.dev"
source: hosted
version: "2.1.3+1"
version: "2.2.1"
mobx_codegen:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181"
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.3.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
package_config:
dependency: transitive
description:
@ -481,74 +506,74 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
url: "https://pub.dev"
source: hosted
version: "2.0.12"
version: "2.1.1"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
url: "https://pub.dev"
source: hosted
version: "2.0.22"
version: "2.2.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74"
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.3.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.1.7"
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
url: "https://pub.dev"
source: hosted
version: "2.0.5"
version: "2.1.1"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.2.1"
platform:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.3"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.7"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.6.2"
version: "3.7.3"
pool:
dependency: transitive
description:
@ -557,30 +582,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
process:
provider:
dependency: transitive
description:
name: process
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
url: "https://pub.dev"
source: hosted
version: "4.2.4"
version: "6.1.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a"
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.3"
rxdart:
dependency: "direct main"
description:
@ -593,18 +618,18 @@ packages:
dependency: transitive
description:
name: shelf
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
version: "1.0.4"
sky_engine:
dependency: transitive
description: flutter
@ -694,10 +719,10 @@ packages:
dependency: transitive
description:
name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.1"
version: "1.3.2"
unorm_dart:
dependency: "direct main"
description:
@ -718,42 +743,42 @@ packages:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
version: "1.1.0"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.4.0"
win32:
dependency: transitive
description:
name: win32
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
version: "5.0.9"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
url: "https://pub.dev"
source: hosted
version: "0.2.0+3"
version: "1.0.3"
yaml:
dependency: transitive
description:
name: yaml
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
flutter: ">=3.0.0"
dart: ">=3.0.6 <4.0.0"
flutter: ">=3.7.0"

View file

@ -22,7 +22,7 @@ dependencies:
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
ref: silent-payments
bitbox:
git:
url: https://github.com/cake-tech/bitbox-flutter.git

View file

@ -86,7 +86,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password,
}) async {
final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password);
return BitcoinCashWallet(
mnemonic: snp.mnemonic,
password: password,

View file

@ -29,6 +29,12 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
networkType: networkType);
@override
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
String getAddress(
{required int index, required bitcoin.HDWallet hd, bitcoin.AddressType? addressType}) =>
generateP2PKHAddress(hd: hd, index: index, networkType: networkType);
@override
Future<void> setAddressType(dynamic type) {
throw UnimplementedError();
}
}

View file

@ -31,7 +31,7 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
@override
Future<BitcoinCashWallet> create(
credentials) async {
credentials, {bool? isTestnet}) async {
final strength = (credentials.seedPhraseLength == 12)
? 128
: (credentials.seedPhraseLength == 24)

View file

@ -24,7 +24,7 @@ dependencies:
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
ref: silent-payments
bitbox:
git:
url: https://github.com/cake-tech/bitbox-flutter.git

View file

@ -14,6 +14,16 @@ class SyncingSyncStatus extends SyncStatus {
@override
String toString() => '$blocksLeft';
factory SyncingSyncStatus.fromHeightValues(int chainTip, int initialSyncHeight, int syncHeight) {
final track = chainTip - initialSyncHeight;
final diff = track - (chainTip - syncHeight);
final ptc = diff <= 0 ? 0.0 : diff / track;
final left = chainTip - syncHeight;
// sum 1 because if at the chain tip, will say "0 blocks left"
return SyncingSyncStatus(left + 1, ptc);
}
}
class SyncedSyncStatus extends SyncStatus {
@ -51,4 +61,6 @@ class ConnectedSyncStatus extends SyncStatus {
class LostConnectionSyncStatus extends SyncStatus {
@override
double progress() => 1.0;
}
@override
String toString() => 'Reconnecting';
}

View file

@ -16,5 +16,6 @@ class Unspent {
bool isFrozen;
String note;
bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');
bool get isP2wpkh =>
address.startsWith('bc') || address.startsWith('tb') || address.startsWith('ltc');
}

View file

@ -3,8 +3,8 @@ import 'package:cw_core/wallet_info.dart';
abstract class WalletAddresses {
WalletAddresses(this.walletInfo)
: addressesMap = {},
addressInfos = {};
: addressesMap = {},
addressInfos = {};
final WalletInfo walletInfo;
@ -38,4 +38,7 @@ abstract class WalletAddresses {
}
bool containsAddress(String address) => addressesMap.containsKey(address);
dynamic addressPageType;
Future<void> setAddressType(dynamic type);
}

View file

@ -8,7 +8,7 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
RFK extends WalletCredentials> {
WalletType getType();
Future<WalletBase> create(N credentials);
Future<WalletBase> create(N credentials, {bool? isTestnet});
Future<WalletBase> restoreFromSeed(RFS credentials);

View file

@ -30,4 +30,9 @@ abstract class EthereumWalletAddressesBase extends WalletAddresses with Store {
print(e.toString());
}
}
@override
Future<void> setAddressType(dynamic type) {
throw UnimplementedError();
}
}

View file

@ -19,7 +19,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
final Box<WalletInfo> walletInfoSource;
@override
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials, {bool? isTestnet}) async {
final strength = (credentials.seedPhraseLength == 12)
? 128

View file

@ -88,4 +88,9 @@ abstract class HavenWalletAddressesBase extends WalletAddressesWithAccount<Accou
@override
bool containsAddress(String address) =>
addressInfos[account?.id ?? 0]?.any((it) => it.address == address) ?? false;
@override
Future<void> setAddressType(dynamic type) {
throw UnimplementedError();
}
}

View file

@ -68,7 +68,7 @@ class HavenWalletService extends WalletService<
WalletType getType() => WalletType.haven;
@override
Future<HavenWallet> create(HavenNewWalletCredentials credentials) async {
Future<HavenWallet> create(HavenNewWalletCredentials credentials, {bool? isTestnet}) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
await haven_wallet_manager.createWallet(

View file

@ -116,4 +116,9 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
@override
bool containsAddress(String address) =>
addressInfos[account?.id ?? 0]?.any((it) => it.address == address) ?? false;
@override
Future<void> setAddressType(dynamic type) {
throw UnimplementedError();
}
}

View file

@ -65,7 +65,7 @@ class MoneroWalletService extends WalletService<
WalletType getType() => WalletType.monero;
@override
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
await monero_wallet_manager.createWallet(

View file

@ -47,4 +47,9 @@ abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
print(e.toString());
}
}
@override
Future<void> setAddressType(dynamic type) {
throw UnimplementedError();
}
}

View file

@ -26,7 +26,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
WalletType getType() => WalletType.nano;
@override
Future<WalletBase> create(NanoNewWalletCredentials credentials) async {
Future<WalletBase> create(NanoNewWalletCredentials credentials, {bool? isTestnet}) async {
// nano standard:
DerivationType derivationType = DerivationType.nano;
String seedKey = NanoSeeds.generateSeed();

View file

@ -142,27 +142,9 @@ Then we need to generate localization files.
`$ flutter packages pub run tool/generate_localization.dart`
Lastly, we will generate mobx models for the project.
Generate mobx models for `cw_core`:
`cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_monero`:
`cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_bitcoin`:
`cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_haven`:
`cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Finally build mobx models for the app:
`$ flutter packages pub run build_runner build --delete-conflicting-outputs`
`$ ./model_generator.sh`
### 9. Build!

View file

@ -63,9 +63,9 @@ class CWBitcoin extends Bitcoin {
}
@override
Future<void> generateNewAddress(Object wallet) async {
Future<void> generateNewAddress(Object wallet, {String? label}) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.walletAddresses.generateNewAddress();
await bitcoinWallet.walletAddresses.generateNewAddress(label: label);
}
@override
@ -105,6 +105,21 @@ class CWBitcoin extends Bitcoin {
return bitcoinWallet.walletAddresses.address;
}
String getReceiveAddress(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.receiveAddress;
}
btc.SilentPaymentAddress? getSilentAddress(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.silentAddress;
}
List<BitcoinAddressRecord> getSilentAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.silentAddresses;
}
@override
String formatterBitcoinAmountToString({required int amount})
=> bitcoinAmountToString(amount: amount);
@ -155,4 +170,4 @@ class CWBitcoin extends Bitcoin {
@override
TransactionPriority getLitecoinTransactionPrioritySlow()
=> LitecoinTransactionPriority.slow;
}
}

View file

@ -8,9 +8,8 @@ class AddressValidator extends TextValidator {
AddressValidator({required CryptoCurrency type})
: super(
errorMessage: S.current.error_text_address,
useAdditionalValidation: type == CryptoCurrency.btc
? bitcoin.Address.validateAddress
: null,
useAdditionalValidation:
type == CryptoCurrency.btc ? bitcoin.Address.validateAddress : null,
pattern: getPattern(type),
length: getLength(type));
@ -25,7 +24,9 @@ class AddressValidator extends TextValidator {
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
case CryptoCurrency.btc:
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
final silentpayments = '^tsp1[0-9a-zA-Z]{113}\$';
return '${bitcoin.P2pkhAddress.REGEX.pattern}|${bitcoin.P2shAddress.REGEX.pattern}|${bitcoin.P2wpkhAddress.REGEX.pattern}|${bitcoin.P2trAddress.REGEX.pattern}|$silentpayments';
case CryptoCurrency.nano:
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.banano:
@ -88,7 +89,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.dai:
case CryptoCurrency.dash:
case CryptoCurrency.eos:
return '[0-9a-zA-Z]';
return '[0-9a-zA-Z]';
case CryptoCurrency.bch:
return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q[0-9a-zA-Z]{42}\$|^bitcoincash:q[0-9a-zA-Z]{41}\$|^bitcoincash:q[0-9a-zA-Z]{42}\$';
case CryptoCurrency.bnb:
@ -284,4 +285,5 @@ class AddressValidator extends TextValidator {
return null;
}
}
}
}

View file

@ -0,0 +1,23 @@
final SecureStorage secureStorageShared = FakeSecureStorage();
abstract class SecureStorage {
Future<String?> read({required String key});
Future<void> write({required String key, required String? value});
Future<void> delete({required String key});
// Legacy
Future<String?> readNoIOptions({required String key});
}
class FakeSecureStorage extends SecureStorage {
@override
Future<String?> read({required String key}) async => null;
@override
Future<void> write({required String key, required String? value}) async {}
@override
Future<void> delete({required String key}) async {}
@override
Future<String?> readNoIOptions({required String key}) async => null;
}

View file

@ -3,7 +3,9 @@ import 'package:cw_core/sync_status.dart';
String syncStatusTitle(SyncStatus syncStatus) {
if (syncStatus is SyncingSyncStatus) {
return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
return syncStatus.blocksLeft == 1
? S.current.Block_remaining('${syncStatus.blocksLeft}')
: S.current.Blocks_remaining('${syncStatus.blocksLeft}');
}
if (syncStatus is SyncedSyncStatus) {
@ -35,4 +37,4 @@ String syncStatusTitle(SyncStatus syncStatus) {
}
return '';
}
}

View file

@ -55,7 +55,7 @@ class WalletCreationService {
}
}
Future<WalletBase> create(WalletCredentials credentials) async {
Future<WalletBase> create(WalletCredentials credentials, {bool? isTestnet}) async {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
@ -63,7 +63,7 @@ class WalletCreationService {
credentials.seedPhraseLength = settingsStore.seedPhraseLength.value;
}
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.create(credentials);
final wallet = await _service!.create(credentials, isTestnet: isTestnet);
if (wallet.type == WalletType.monero) {
await sharedPreferences.setBool(

View file

@ -222,6 +222,7 @@ import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc;
import 'core/totp_request_details.dart';
import 'src/screens/settings/desktop_settings/desktop_settings_page.dart';
@ -341,10 +342,9 @@ Future<void> setup({
getIt.get<KeyService>(),
(WalletType type) => getIt.get<WalletService>(param1: type)));
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) =>
WalletNewVM(getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type));
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) => WalletNewVM(
getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type));
getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) {
return WalletRestorationFromQRVM(getIt.get<AppStore>(),
@ -524,7 +524,7 @@ Future<void> setup({
getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage());
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
getIt.registerFactoryParam<ReceiveOptionViewModel, dynamic, void>(
(pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
getIt.registerFactoryParam<AnonInvoicePageViewModel, List<dynamic>, void>((args, _) {
@ -642,7 +642,10 @@ Future<void> setup({
getIt.registerFactory<MoneroAccountListViewModel>(() {
final wallet = getIt.get<AppStore>().wallet!;
if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
if ((wallet.type == WalletType.bitcoin &&
wallet.walletAddresses.addressPageType == btc.AddressType.p2sp) ||
wallet.type == WalletType.monero ||
wallet.type == WalletType.haven) {
return MoneroAccountListViewModel(wallet);
}
throw Exception(
@ -900,8 +903,8 @@ Future<void> setup({
(param1, isCreate) => NewWalletTypePage(onTypeSelected: param1, isCreate: isCreate ?? true));
getIt.registerFactoryParam<PreSeedPage, WalletType, AdvancedPrivacySettingsViewModel>(
(WalletType type, AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel)
=> PreSeedPage(type, advancedPrivacySettingsViewModel));
(WalletType type, AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel) =>
PreSeedPage(type, advancedPrivacySettingsViewModel));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
TradeDetailsViewModel(
@ -995,11 +998,10 @@ Future<void> setup({
getIt.registerFactory(() => YatService());
getIt.registerFactory(() =>
AddressResolver(
yatService: getIt.get<YatService>(),
wallet: getIt.get<AppStore>().wallet!,
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactory(() => AddressResolver(
yatService: getIt.get<YatService>(),
wallet: getIt.get<AppStore>().wallet!,
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
@ -1175,6 +1177,5 @@ Future<void> setup({
getIt.registerFactory(
() => WalletConnectConnectionsView(web3walletService: getIt.get<Web3WalletService>()));
_isSetupFinished = true;
}

View file

@ -28,6 +28,7 @@ import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
class AddressPage extends BasePage {
AddressPage({
@ -70,7 +71,7 @@ class AddressPage extends BasePage {
size: 16,
);
final _closeButton =
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI;
@ -161,10 +162,10 @@ class AddressPage extends BasePage {
Observer(builder: (_) {
if (addressListViewModel.hasAddressList) {
return GestureDetector(
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled
onTap: () async => !addressListViewModel.hasSilentAddresses &&
dashboardViewModel.isAutoGenerateSubaddressesEnabled
? await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>())
context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
: Navigator.of(context).pushNamed(Routes.receive),
child: Container(
height: 50,
@ -184,26 +185,30 @@ class AddressPage extends BasePage {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Observer(
builder: (_) {
String label = addressListViewModel.hasAccounts
? S.of(context).accounts_subaddresses
: S.of(context).addresses;
builder: (_) {
String label = addressListViewModel.hasSilentAddresses
? S.of(context).labeled_silent_addresses
: addressListViewModel.hasAccounts
? S.of(context).accounts_subaddresses
: S.of(context).addresses;
if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) {
label = addressListViewModel.hasAccounts
? S.of(context).accounts
: S.of(context).account;
}
return Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<SyncIndicatorTheme>()!
.textColor),
);
},),
if (!addressListViewModel.hasSilentAddresses &&
dashboardViewModel.isAutoGenerateSubaddressesEnabled) {
label = addressListViewModel.hasAccounts
? S.of(context).accounts
: S.of(context).account;
}
return Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<SyncIndicatorTheme>()!
.textColor),
);
},
),
Icon(
Icons.arrow_forward_ios,
size: 14,
@ -213,7 +218,8 @@ class AddressPage extends BasePage {
),
),
);
} else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled || addressListViewModel.showElectrumAddressDisclaimer) {
} else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled ||
addressListViewModel.showElectrumAddressDisclaimer) {
return Text(S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: TextStyle(
@ -233,7 +239,7 @@ class AddressPage extends BasePage {
return;
}
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (dynamic option) async {
switch (option) {
case ReceivePageOption.anonPayInvoice:
Navigator.pushNamed(
@ -265,6 +271,12 @@ class AddressPage extends BasePage {
);
}
break;
case bitcoin.AddressType.p2pkh:
case bitcoin.AddressType.p2wpkh:
case bitcoin.AddressType.p2tr:
case bitcoin.AddressType.p2sp:
await addressListViewModel.setAddressType(option);
break;
default:
}
});

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/routes.dart';
@ -125,15 +126,20 @@ class _WalletNameFormState extends State<WalletNameForm> {
hintStyle: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor),
color:
Theme.of(context).extension<NewWalletTheme>()!.hintTextColor),
hintText: S.of(context).wallet_name,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
color: Theme.of(context)
.extension<NewWalletTheme>()!
.underlineColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
color: Theme.of(context)
.extension<NewWalletTheme>()!
.underlineColor,
width: 1.0),
),
suffixIcon: Semantics(
@ -160,7 +166,9 @@ class _WalletNameFormState extends State<WalletNameForm> {
height: 34,
child: Image.asset(
'assets/images/refresh_icon.png',
color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor,
),
),
),
@ -168,6 +176,12 @@ class _WalletNameFormState extends State<WalletNameForm> {
),
validator: WalletNameValidator(),
),
Observer(builder: (context) {
return SettingsSwitcherCell(
title: S.current.use_testnet,
value: widget._walletNewVM.useTestnet,
onValueChange: (_, __) => widget._walletNewVM.toggleUseTestnet());
}),
],
),
),

View file

@ -190,7 +190,7 @@ class AnonPayInvoicePage extends BasePage {
return;
}
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (dynamic option) {
switch (option) {
case ReceivePageOption.mainnet:
Navigator.popAndPushNamed(context, Routes.addressPage);

View file

@ -24,6 +24,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
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:keyboard_actions/keyboard_actions.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
class ReceivePage extends BasePage {
ReceivePage({required this.addressListViewModel})
@ -67,8 +68,7 @@ class ReceivePage extends BasePage {
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) =>
GradientBackground(scaffold: scaffold);
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
@override
Widget trailing(BuildContext context) {
@ -99,7 +99,10 @@ class ReceivePage extends BasePage {
@override
Widget body(BuildContext context) {
return (addressListViewModel.type == WalletType.monero ||
return ((addressListViewModel.type == WalletType.bitcoin &&
addressListViewModel.wallet.walletAddresses.addressPageType ==
bitcoin.AddressType.p2sp) ||
addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven ||
addressListViewModel.type == WalletType.nano ||
addressListViewModel.type == WalletType.banano)
@ -156,7 +159,8 @@ class ReceivePage extends BasePage {
icon: Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
color:
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
));
}
@ -164,11 +168,12 @@ class ReceivePage extends BasePage {
cell = HeaderTile(
onTap: () =>
Navigator.of(context).pushNamed(Routes.newSubaddress),
title: S.of(context).addresses,
title: S.of(context).labeled_silent_addresses,
icon: Icon(
Icons.add,
size: 20,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
color:
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
));
}
@ -177,11 +182,19 @@ class ReceivePage extends BasePage {
final isCurrent =
item.address == addressListViewModel.address.address;
final backgroundColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
? Theme.of(context)
.extension<ReceivePageTheme>()!
.currentTileBackgroundColor
: Theme.of(context)
.extension<ReceivePageTheme>()!
.tilesBackgroundColor;
final textColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
? Theme.of(context)
.extension<ReceivePageTheme>()!
.currentTileTextColor
: Theme.of(context)
.extension<ReceivePageTheme>()!
.tilesTextColor;
return AddressCell.fromItem(item,
isCurrent: isCurrent,
@ -202,6 +215,15 @@ class ReceivePage extends BasePage {
child: cell,
);
})),
Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text(S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color:
Theme.of(context).extension<BalancePageTheme>()!.labelTextColor)),
),
],
),
))

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/crypto_currency.dart';
@ -164,7 +165,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
if (DeviceInfo.instance.isMobile) AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor:

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
@ -130,8 +131,7 @@ class AddressTextField extends StatelessWidget {
),
)),
],
if (this.options.contains(AddressTextFieldOption.qrCode) &&
DeviceInfo.instance.isMobile) ...[
if (this.options.contains(AddressTextFieldOption.qrCode)) ...[
Container(
width: prefixIconWidth,
height: prefixIconHeight,
@ -191,7 +191,7 @@ class AddressTextField extends StatelessWidget {
Future<void> _presentQRScanner(BuildContext context) async {
bool isCameraPermissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
await PermissionHandler.checkPermission(Permission.camera, context);
if (!isCameraPermissionGranted) return;
final code = await presentQRScanner();
if (code.isEmpty) {

View file

@ -31,6 +31,7 @@ import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
part 'dashboard_view_model.g.dart';
@ -260,7 +261,11 @@ abstract class DashboardViewModelBase with Store {
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
bool get hasRescan =>
(wallet.type == WalletType.bitcoin &&
wallet.walletAddresses.addressPageType == bitcoin.AddressType.p2sp) ||
wallet.type == WalletType.monero ||
wallet.type == WalletType.haven;
BalanceViewModel balanceViewModel;

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
part 'receive_option_view_model.g.dart';
@ -9,26 +10,38 @@ class ReceiveOptionViewModel = ReceiveOptionViewModelBase with _$ReceiveOptionVi
abstract class ReceiveOptionViewModelBase with Store {
ReceiveOptionViewModelBase(this._wallet, this.initialPageOption)
: selectedReceiveOption = initialPageOption ?? ReceivePageOption.mainnet,
: selectedReceiveOption = initialPageOption ??
(_wallet.type == WalletType.bitcoin
? _wallet.walletAddresses.addressPageType
: ReceivePageOption.mainnet),
_options = [] {
final walletType = _wallet.type;
_options =
walletType == WalletType.haven ? [ReceivePageOption.mainnet] : ReceivePageOption.values;
_options = walletType == WalletType.haven
? [ReceivePageOption.mainnet]
: walletType == WalletType.bitcoin
? [
bitcoin.AddressType.p2pkh,
bitcoin.AddressType.p2wpkh,
bitcoin.AddressType.p2tr,
bitcoin.AddressType.p2sp,
...ReceivePageOption.values.where((element) => element != ReceivePageOption.mainnet)
]
: ReceivePageOption.values;
}
final WalletBase _wallet;
final ReceivePageOption? initialPageOption;
final dynamic initialPageOption;
List<ReceivePageOption> _options;
List<dynamic> _options;
@observable
ReceivePageOption selectedReceiveOption;
dynamic selectedReceiveOption;
List<ReceivePageOption> get options => _options;
List<dynamic> get options => _options;
@action
void selectReceiveOption(ReceivePageOption option) {
void selectReceiveOption(dynamic option) {
selectedReceiveOption = option;
}
}

View file

@ -12,12 +12,10 @@ import 'package:permission_handler/permission_handler.dart';
part 'node_create_or_edit_view_model.g.dart';
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase
with _$NodeCreateOrEditViewModel;
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase with _$NodeCreateOrEditViewModel;
abstract class NodeCreateOrEditViewModelBase with Store {
NodeCreateOrEditViewModelBase(
this._nodeSource, this._walletType, this._settingsStore)
NodeCreateOrEditViewModelBase(this._nodeSource, this._walletType, this._settingsStore)
: state = InitialExecutionState(),
connectionState = InitialExecutionState(),
useSSL = false,
@ -65,6 +63,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
bool get hasAuthCredentials =>
_walletType == WalletType.monero || _walletType == WalletType.haven;
bool get hasTestnetSupport => _walletType == WalletType.bitcoin;
String get uri {
var uri = address;
@ -181,7 +181,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
Future<void> scanQRCodeForNewNode(BuildContext context) async {
try {
bool isCameraPermissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
await PermissionHandler.checkPermission(Permission.camera, context);
if (!isCameraPermissionGranted) return;
String code = await presentQRScanner();
@ -196,7 +196,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
}
final userInfo = uri.userInfo.split(':');
if (userInfo.length < 2) {
throw Exception('Unexpected scan QR code value: Value is invalid');
}

View file

@ -1,4 +1,5 @@
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
part 'rescan_view_model.g.dart';
@ -9,8 +10,8 @@ enum RescanWalletState { rescaning, none }
abstract class RescanViewModelBase with Store {
RescanViewModelBase(this._wallet)
: state = RescanWalletState.none,
isButtonEnabled = false;
: state = RescanWalletState.none,
isButtonEnabled = false;
final WalletBase _wallet;
@ -23,8 +24,8 @@ abstract class RescanViewModelBase with Store {
@action
Future<void> rescanCurrentWallet({required int restoreHeight}) async {
state = RescanWalletState.rescaning;
await _wallet.rescan(height: restoreHeight);
_wallet.transactionHistory.clear();
_wallet.rescan(height: restoreHeight);
if (_wallet.type != WalletType.bitcoin) _wallet.transactionHistory.clear();
state = RescanWalletState.none;
}
}
}

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_core/wallet_base.dart';
@ -27,8 +28,7 @@ class AddressEditOrCreateStateFailure extends AddressEditOrCreateState {
}
abstract class WalletAddressEditOrCreateViewModelBase with Store {
WalletAddressEditOrCreateViewModelBase(
{required WalletBase wallet, WalletAddressListItem? item})
WalletAddressEditOrCreateViewModelBase({required WalletBase wallet, WalletAddressListItem? item})
: isEdit = item != null,
state = AddressEditOrCreateStateInitial(),
label = item?.name ?? '',
@ -68,27 +68,21 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
if (wallet.type == WalletType.bitcoin
|| wallet.type == WalletType.litecoin
|| wallet.type == WalletType.bitcoinCash) {
await bitcoin!.generateNewAddress(wallet);
await bitcoin!.generateNewAddress(wallet, label: label);
await wallet.save();
}
if (wallet.type == WalletType.monero) {
await monero
!.getSubaddressList(wallet)
.addSubaddress(
wallet,
accountIndex: monero!.getCurrentAccount(wallet).id,
label: label);
await monero!
.getSubaddressList(wallet)
.addSubaddress(wallet, accountIndex: monero!.getCurrentAccount(wallet).id, label: label);
await wallet.save();
}
if (wallet.type == WalletType.haven) {
await haven
!.getSubaddressList(wallet)
.addSubaddress(
wallet,
accountIndex: haven!.getCurrentAccount(wallet).id,
label: label);
await haven!
.getSubaddressList(wallet)
.addSubaddress(wallet, accountIndex: haven!.getCurrentAccount(wallet).id, label: label);
await wallet.save();
}
}
@ -109,9 +103,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
}
if (wallet.type == WalletType.haven) {
await haven!.getSubaddressList(wallet).setLabelSubaddress(wallet,
accountIndex: haven!.getCurrentAccount(wallet).id,
addressIndex: index,
label: label);
accountIndex: haven!.getCurrentAccount(wallet).id, addressIndex: index, label: label);
await wallet.save();
}
}

View file

@ -15,6 +15,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc;
part 'wallet_address_list_view_model.g.dart';
@ -109,7 +110,7 @@ class EthereumURI extends PaymentURI {
class BitcoinCashURI extends PaymentURI {
BitcoinCashURI({required String amount, required String address})
: super(amount: amount, address: address);
: super(amount: amount, address: address);
@override
String toString() {
var base = address;
@ -120,9 +121,7 @@ class BitcoinCashURI extends PaymentURI {
return base;
}
}
}
class NanoURI extends PaymentURI {
NanoURI({required String amount, required String address})
@ -147,8 +146,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}) : _baseItems = <ListItem>[],
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
hasAccounts =
appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven,
amount = '',
super(appStore: appStore) {
_init();
@ -159,7 +156,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
_init();
selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
hasAccounts = wallet.type == WalletType.monero || wallet.type == WalletType.haven;
_hasAccounts =
hasSilentAddresses || wallet.type == WalletType.monero || wallet.type == WalletType.haven;
}
static const String _cryptoNumberPattern = '0.00000000';
@ -257,13 +255,19 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
if (wallet.type == WalletType.bitcoin) {
final primaryAddress = bitcoin!.getAddress(wallet);
final bitcoinAddresses = bitcoin!.getAddresses(wallet).map((addr) {
final isPrimary = addr == primaryAddress;
final receiveAddress = bitcoin!.getReceiveAddress(wallet);
addressList.add(
WalletAddressListItem(isPrimary: true, name: 'Primary address', address: receiveAddress));
return WalletAddressListItem(isPrimary: isPrimary, name: null, address: addr);
final silentAddress = bitcoin!.getSilentAddress(wallet).toString();
addressList.add(
WalletAddressListItem(isPrimary: false, name: silentAddress, address: silentAddress));
final silentAddresses = bitcoin!.getSilentAddresses(wallet);
silentAddresses.forEach((addr) {
addressList.add(WalletAddressListItem(
isPrimary: false, name: addr.silentAddressLabel, address: addr.address));
});
addressList.addAll(bitcoinAddresses);
}
if (wallet.type == WalletType.ethereum) {
@ -276,7 +280,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
@observable
bool hasAccounts;
bool _hasAccounts = false;
@computed
bool get hasAccounts => _hasAccounts;
@computed
String get accountLabel {
@ -291,18 +298,31 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return '';
}
@observable
// ignore: prefer_final_fields
bool? _hasSilentAddresses = null;
@computed
bool get hasSilentAddresses =>
_hasSilentAddresses ??
wallet.type == WalletType.bitcoin &&
wallet.walletAddresses.addressPageType == btc.AddressType.p2sp;
@computed
bool get hasAddressList =>
hasSilentAddresses ||
wallet.type == WalletType.monero ||
wallet.type == WalletType.haven;/* ||
wallet.type ==
WalletType
.haven; /* ||
wallet.type == WalletType.nano ||
wallet.type == WalletType.banano;*/// TODO: nano accounts are disabled for now
wallet.type == WalletType.banano;*/ // TODO: nano accounts are disabled for now
@computed
bool get showElectrumAddressDisclaimer =>
wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash;
(wallet.type == WalletType.bitcoin && !hasSilentAddresses) ||
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash;
List<ListItem> _baseItems;
@ -312,13 +332,22 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
void setAddress(WalletAddressListItem address) =>
wallet.walletAddresses.address = address.address;
@action
Future<void> setAddressType(dynamic option) async {
await wallet.walletAddresses.setAddressType(option);
_hasSilentAddresses = option == btc.AddressType.p2sp;
}
void _init() {
_baseItems = [];
if (wallet.type == WalletType.monero ||
wallet.type == WalletType.haven /*||
wallet.type ==
WalletType
.haven /*||
wallet.type == WalletType.nano ||
wallet.type == WalletType.banano*/) {
wallet.type == WalletType.banano*/
) {
_baseItems.add(WalletAccountListHeader());
}

View file

@ -39,7 +39,7 @@ abstract class WalletCreationVMBase with Store {
bool typeExists(WalletType type) => walletCreationService.typeExists(type);
Future<void> create({dynamic options, RestoredWallet? restoreWallet}) async {
Future<void> create({dynamic options, RestoredWallet? restoreWallet, bool? isTestnet}) async {
final type = restoreWallet?.type ?? this.type;
try {
state = IsExecutingState();

View file

@ -27,6 +27,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
: selectedMnemonicLanguage = '',
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: false);
@observable
bool _useTestnet = false;
@computed
bool get useTestnet => _useTestnet;
@observable
String selectedMnemonicLanguage;
@ -59,6 +65,9 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
@override
Future<WalletBase> process(WalletCredentials credentials) async {
walletCreationService.changeWalletType(type: type);
return walletCreationService.create(credentials);
return walletCreationService.create(credentials, isTestnet: useTestnet);
}
@action
void toggleUseTestnet() => _useTestnet = !_useTestnet;
}

View file

@ -1,8 +1,10 @@
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs
#!/bin/bash
cd cw_core; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
cd cw_monero; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
cd cw_bitcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
cd cw_haven; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
cd cw_ethereum; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
cd cw_nano; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
cd cw_bitcoin_cash; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
dart run build_runner build --delete-conflicting-outputs

View file

@ -96,7 +96,7 @@ dependencies:
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
ref: silent-payments
fluttertoast: 8.1.4
dev_dependencies:

View file

@ -730,5 +730,8 @@
"seed_phrase_length": " ﺭﻭﺬﺒﻟﺍ ﺓﺭﺎﺒﻌﻟﺍ ﻝﻮﻃ",
"unavailable_balance": " ﺮﻓﻮﺘﻣ ﺮﻴﻏ ﺪﻴﺻﺭ",
"unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ",
"unspent_change": "يتغير"
}
"unspent_change": "يتغير",
"Block_remaining": "${status} كتلة متبقية",
"labeled_silent_addresses": "العناوين الصامتة المسمى",
"use_testnet": "استخدم testnet"
}

View file

@ -726,5 +726,8 @@
"seed_phrase_length": "Дължина на началната фраза",
"unavailable_balance": "Неналично салдо",
"unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.",
"unspent_change": "Промяна"
}
"unspent_change": "Промяна",
"Block_remaining": "${status} останал блок",
"labeled_silent_addresses": "Етикетирани безшумни адреси",
"use_testnet": "Използвайте TestNet"
}

View file

@ -726,5 +726,8 @@
"seed_phrase_length": "Délka fráze semene",
"unavailable_balance": "Nedostupný zůstatek",
"unavailable_balance_description": "Nedostupný zůstatek: Tento součet zahrnuje prostředky, které jsou uzamčeny v nevyřízených transakcích a ty, které jste aktivně zmrazili v nastavení kontroly mincí. Uzamčené zůstatky budou k dispozici po dokončení příslušných transakcí, zatímco zmrazené zůstatky zůstanou pro transakce nepřístupné, dokud se nerozhodnete je uvolnit.",
"unspent_change": "Změna"
}
"unspent_change": "Změna",
"Block_remaining": "${status} Blok zbývající",
"labeled_silent_addresses": "Označené tiché adresy",
"use_testnet": "Použijte testNet"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "Länge der Seed-Phrase",
"unavailable_balance": "Nicht verfügbares Guthaben",
"unavailable_balance_description": "Nicht verfügbares Guthaben: Diese Summe umfasst Gelder, die in ausstehenden Transaktionen gesperrt sind, und solche, die Sie in Ihren Münzkontrolleinstellungen aktiv eingefroren haben. Gesperrte Guthaben werden verfügbar, sobald die entsprechenden Transaktionen abgeschlossen sind, während eingefrorene Guthaben für Transaktionen nicht zugänglich bleiben, bis Sie sich dazu entschließen, sie wieder freizugeben.",
"unspent_change": "Wechselgeld"
}
"unspent_change": "Wechselgeld",
"Block_remaining": "${status} Block verbleibend",
"labeled_silent_addresses": "Bezeichnete stille Adressen",
"use_testnet": "TESTNET verwenden"
}

View file

@ -735,5 +735,8 @@
"seed_phrase_length": "Seed phrase length",
"unavailable_balance": "Unavailable balance",
"unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your coin control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.",
"unspent_change": "Change"
}
"unspent_change": "Change",
"Block_remaining": "${status} Block Remaining",
"labeled_silent_addresses": "Labeled Silent Addresses",
"use_testnet": "Use testnet"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "Longitud de la frase inicial",
"unavailable_balance": "Saldo no disponible",
"unavailable_balance_description": "Saldo no disponible: este total incluye fondos que están bloqueados en transacciones pendientes y aquellos que usted ha congelado activamente en su configuración de control de monedas. Los saldos bloqueados estarán disponibles una vez que se completen sus respectivas transacciones, mientras que los saldos congelados permanecerán inaccesibles para las transacciones hasta que usted decida descongelarlos.",
"unspent_change": "Cambiar"
}
"unspent_change": "Cambiar",
"Block_remaining": "${status} bloque restante",
"labeled_silent_addresses": "Direcciones silenciosas etiquetadas",
"use_testnet": "Use TestNet"
}

View file

@ -734,5 +734,8 @@
"unavailable_balance_description": "Solde indisponible : ce total comprend les fonds bloqués dans les transactions en attente et ceux que vous avez activement gelés dans vos paramètres de contrôle des pièces. Les soldes bloqués deviendront disponibles une fois leurs transactions respectives terminées, tandis que les soldes gelés resteront inaccessibles aux transactions jusqu'à ce que vous décidiez de les débloquer.",
"camera_permission_is_required": "L'autorisation d'accès à la caméra est requise.\nVeuillez l'activer depuis les paramètres de l'application.",
"switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer",
"unspent_change": "Changement"
}
"unspent_change": "Changement",
"Block_remaining": "${status} bloc restant",
"labeled_silent_addresses": "Adresses silencieuses étiquetées",
"use_testnet": "Utiliser TestNet"
}

View file

@ -712,5 +712,8 @@
"seed_phrase_length": "Tsawon jimlar iri",
"unavailable_balance": "Ma'aunin da ba ya samuwa",
"unavailable_balance_description": "Ma'auni Babu: Wannan jimlar ya haɗa da kuɗi waɗanda ke kulle a cikin ma'amaloli da ke jiran aiki da waɗanda kuka daskare sosai a cikin saitunan sarrafa kuɗin ku. Ma'auni da aka kulle za su kasance da zarar an kammala ma'amalolinsu, yayin da daskararrun ma'auni ba za su iya samun damar yin ciniki ba har sai kun yanke shawarar cire su.",
"unspent_change": "Canza"
}
"unspent_change": "Canza",
"Block_remaining": "${status} toshe ragowar",
"labeled_silent_addresses": "Mai labarar adireshin shiru",
"use_testnet": "Amfani da gwaji"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "बीज वाक्यांश की लंबाई",
"unavailable_balance": "अनुपलब्ध शेष",
"unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।",
"unspent_change": "परिवर्तन"
}
"unspent_change": "परिवर्तन",
"Block_remaining": "${status} शेष ब्लॉक",
"labeled_silent_addresses": "मूक पते लेबल",
"use_testnet": "टेस्टनेट का उपयोग करें"
}

View file

@ -732,5 +732,8 @@
"seed_phrase_length": "Duljina početne fraze",
"unavailable_balance": "Nedostupno stanje",
"unavailable_balance_description": "Nedostupno stanje: Ovaj ukupni iznos uključuje sredstva koja su zaključana u transakcijama na čekanju i ona koja ste aktivno zamrznuli u postavkama kontrole novčića. Zaključani saldi postat će dostupni kada se dovrše njihove transakcije, dok zamrznuti saldi ostaju nedostupni za transakcije sve dok ih ne odlučite odmrznuti.",
"unspent_change": "Promijeniti"
}
"unspent_change": "Promijeniti",
"Block_remaining": "${status} ostao blok",
"labeled_silent_addresses": "Označene tihe adrese",
"use_testnet": "Koristite TestNet"
}

View file

@ -722,5 +722,8 @@
"seed_phrase_length": "Panjang frase benih",
"unavailable_balance": "Saldo tidak tersedia",
"unavailable_balance_description": "Saldo Tidak Tersedia: Total ini termasuk dana yang terkunci dalam transaksi yang tertunda dan dana yang telah Anda bekukan secara aktif di pengaturan kontrol koin Anda. Saldo yang terkunci akan tersedia setelah transaksi masing-masing selesai, sedangkan saldo yang dibekukan tetap tidak dapat diakses untuk transaksi sampai Anda memutuskan untuk mencairkannya.",
"unspent_change": "Mengubah"
}
"unspent_change": "Mengubah",
"Block_remaining": "${status} blok tersisa",
"labeled_silent_addresses": "Label alamat diam",
"use_testnet": "Gunakan TestNet"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "Lunghezza della frase seed",
"unavailable_balance": "Saldo non disponibile",
"unavailable_balance_description": "Saldo non disponibile: questo totale include i fondi bloccati nelle transazioni in sospeso e quelli che hai congelato attivamente nelle impostazioni di controllo delle monete. I saldi bloccati diventeranno disponibili una volta completate le rispettive transazioni, mentre i saldi congelati rimarranno inaccessibili per le transazioni finché non deciderai di sbloccarli.",
"unspent_change": "Modifica"
}
"unspent_change": "Modifica",
"Block_remaining": "${status} blocco rimanente",
"labeled_silent_addresses": "Indirizzi silenziosi etichettati",
"use_testnet": "Usa TestNet"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "シードフレーズの長さ",
"unavailable_balance": "利用できない残高",
"unavailable_balance_description": "利用不可能な残高: この合計には、保留中のトランザクションにロックされている資金と、コイン管理設定でアクティブに凍結した資金が含まれます。ロックされた残高は、それぞれの取引が完了すると利用可能になりますが、凍結された残高は、凍結を解除するまで取引にアクセスできません。",
"unspent_change": "変化"
}
"unspent_change": "変化",
"Block_remaining": "${status}ブロックの残り",
"labeled_silent_addresses": "サイレントアドレスとラベル付けされています",
"use_testnet": "TestNetを使用します"
}

View file

@ -732,5 +732,8 @@
"seed_phrase_length": "시드 문구 길이",
"unavailable_balance": "사용할 수 없는 잔액",
"unavailable_balance_description": "사용할 수 없는 잔액: 이 총계에는 보류 중인 거래에 잠겨 있는 자금과 코인 관리 설정에서 적극적으로 동결된 자금이 포함됩니다. 잠긴 잔액은 해당 거래가 완료되면 사용할 수 있게 되며, 동결된 잔액은 동결을 해제하기 전까지 거래에 액세스할 수 없습니다.",
"unspent_change": "변화"
}
"unspent_change": "변화",
"Block_remaining": "${status} 나머지 블록",
"labeled_silent_addresses": "라벨링 된 무음 주소",
"use_testnet": "TestNet을 사용하십시오"
}

View file

@ -732,5 +732,8 @@
"seed_phrase_length": "မျိုးစေ့စာပိုဒ်တိုအရှည်",
"unavailable_balance": "လက်ကျန်ငွေ မရရှိနိုင်ပါ။",
"unavailable_balance_description": "မရရှိနိုင်သော လက်ကျန်ငွေ- ဤစုစုပေါင်းတွင် ဆိုင်းငံ့ထားသော ငွေပေးငွေယူများတွင် သော့ခတ်ထားသော ငွေကြေးများနှင့် သင်၏ coin ထိန်းချုပ်မှုဆက်တင်များတွင် သင် တက်ကြွစွာ အေးခဲထားသော ငွေများ ပါဝင်သည်။ သော့ခတ်ထားသော လက်ကျန်ငွေများကို ၎င်းတို့၏ သက်ဆိုင်ရာ ငွေပေးငွေယူများ ပြီးမြောက်သည်နှင့် တပြိုင်နက် ရရှိနိုင်မည်ဖြစ်ပြီး၊ အေးခဲထားသော လက်ကျန်များကို ၎င်းတို့အား ပြန်ဖြုတ်ရန် သင်ဆုံးဖြတ်သည်အထိ ငွေပေးငွေယူများအတွက် ဆက်လက်၍မရနိုင်ပါ။",
"unspent_change": "ပေြာင်းလဲခြင်း"
}
"unspent_change": "ပေြာင်းလဲခြင်း",
"Block_remaining": "ကျန်ရှိသော ${status}",
"labeled_silent_addresses": "အသံတိတ်အသံတိတ်လိပ်စာများတံဆိပ်ကပ်",
"use_testnet": "testnet ကိုသုံးပါ"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "Lengte van de zaadzin",
"unavailable_balance": "Onbeschikbaar saldo",
"unavailable_balance_description": "Niet-beschikbaar saldo: Dit totaal omvat het geld dat is vergrendeld in lopende transacties en het geld dat u actief hebt bevroren in uw muntcontrole-instellingen. Vergrendelde saldi komen beschikbaar zodra de betreffende transacties zijn voltooid, terwijl bevroren saldi ontoegankelijk blijven voor transacties totdat u besluit ze weer vrij te geven.",
"unspent_change": "Wijziging"
}
"unspent_change": "Wijziging",
"Block_remaining": "${status} blok resterend",
"labeled_silent_addresses": "Gelabelde stille adressen",
"use_testnet": "Gebruik testnet"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "Długość frazy początkowej",
"unavailable_balance": "Niedostępne saldo",
"unavailable_balance_description": "Niedostępne saldo: Suma ta obejmuje środki zablokowane w transakcjach oczekujących oraz te, które aktywnie zamroziłeś w ustawieniach kontroli monet. Zablokowane salda staną się dostępne po zakończeniu odpowiednich transakcji, natomiast zamrożone salda pozostaną niedostępne dla transakcji, dopóki nie zdecydujesz się ich odblokować.",
"unspent_change": "Zmiana"
}
"unspent_change": "Zmiana",
"Block_remaining": "${status} Block pozostały",
"labeled_silent_addresses": "Oznaczone ciche adresy",
"use_testnet": "Użyj testne"
}

View file

@ -733,5 +733,8 @@
"seed_phrase_length": "Comprimento da frase-semente",
"unavailable_balance": "Saldo indisponível",
"unavailable_balance_description": "Saldo Indisponível: Este total inclui fundos bloqueados em transações pendentes e aqueles que você congelou ativamente nas configurações de controle de moedas. Os saldos bloqueados ficarão disponíveis assim que suas respectivas transações forem concluídas, enquanto os saldos congelados permanecerão inacessíveis para transações até que você decida descongelá-los.",
"unspent_change": "Mudar"
}
"unspent_change": "Mudar",
"Block_remaining": "${status} bloco restante",
"labeled_silent_addresses": "Endereços silenciosos rotulados",
"use_testnet": "Use testNet"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "Длина исходной фразы",
"unavailable_balance": "Недоступный баланс",
"unavailable_balance_description": "Недоступный баланс: в эту сумму входят средства, заблокированные в ожидающих транзакциях, и средства, которые вы активно заморозили в настройках управления монетами. Заблокированные балансы станут доступны после завершения соответствующих транзакций, а замороженные балансы останутся недоступными для транзакций, пока вы не решите их разморозить.",
"unspent_change": "Изменять"
}
"unspent_change": "Изменять",
"Block_remaining": "${status} оставшееся блок",
"labeled_silent_addresses": "Помеченные тихий адреса",
"use_testnet": "Используйте Testnet"
}

View file

@ -732,5 +732,8 @@
"seed_phrase_length": "ความยาววลีของเมล็ด",
"unavailable_balance": "ยอดคงเหลือไม่พร้อมใช้งาน",
"unavailable_balance_description": "ยอดคงเหลือที่ไม่พร้อมใช้งาน: ยอดรวมนี้รวมถึงเงินทุนที่ถูกล็อคในการทำธุรกรรมที่รอดำเนินการและที่คุณได้แช่แข็งไว้ในการตั้งค่าการควบคุมเหรียญของคุณ ยอดคงเหลือที่ถูกล็อคจะพร้อมใช้งานเมื่อธุรกรรมที่เกี่ยวข้องเสร็จสมบูรณ์ ในขณะที่ยอดคงเหลือที่แช่แข็งจะไม่สามารถเข้าถึงได้สำหรับธุรกรรมจนกว่าคุณจะตัดสินใจยกเลิกการแช่แข็ง",
"unspent_change": "เปลี่ยน"
}
"unspent_change": "เปลี่ยน",
"Block_remaining": "${status} เหลือบล็อกที่เหลืออยู่",
"labeled_silent_addresses": "ที่อยู่เงียบที่มีป้ายกำกับ",
"use_testnet": "ใช้ testnet"
}

View file

@ -729,5 +729,8 @@
"seed_phrase_length": "Haba ng parirala ng binhi",
"unavailable_balance": "Hindi available na balanse",
"unavailable_balance_description": "Hindi Available na Balanse: Kasama sa kabuuang ito ang mga pondong naka-lock sa mga nakabinbing transaksyon at ang mga aktibong na-freeze mo sa iyong mga setting ng kontrol ng coin. Magiging available ang mga naka-lock na balanse kapag nakumpleto na ang kani-kanilang mga transaksyon, habang ang mga nakapirming balanse ay nananatiling hindi naa-access para sa mga transaksyon hanggang sa magpasya kang i-unfreeze ang mga ito.",
"unspent_change": "Baguhin"
}
"unspent_change": "Baguhin",
"Block_remaining": "${status} I -block ang natitira",
"labeled_silent_addresses": "May label na tahimik na mga address",
"use_testnet": "Gumamit ng testnet"
}

View file

@ -732,5 +732,8 @@
"seed_phrase_length": "Çekirdek cümle uzunluğu",
"unavailable_balance": "Kullanılamayan bakiye",
"unavailable_balance_description": "Kullanılamayan Bakiye: Bu toplam, bekleyen işlemlerde kilitlenen fonları ve jeton kontrol ayarlarınızda aktif olarak dondurduğunuz fonları içerir. Kilitli bakiyeler, ilgili işlemleri tamamlandıktan sonra kullanılabilir hale gelir; dondurulmuş bakiyeler ise siz onları dondurmaya karar verene kadar işlemler için erişilemez durumda kalır.",
"unspent_change": "Değiştirmek"
}
"unspent_change": "Değiştirmek",
"Block_remaining": "${status} blok kalan blok",
"labeled_silent_addresses": "Etiketli sessiz adresler",
"use_testnet": "TestNet kullanın"
}

View file

@ -734,5 +734,8 @@
"seed_phrase_length": "Довжина початкової фрази",
"unavailable_balance": "Недоступний баланс",
"unavailable_balance_description": "Недоступний баланс: ця сума включає кошти, заблоковані в незавершених транзакціях, і ті, які ви активно заморозили в налаштуваннях контролю монет. Заблоковані баланси стануть доступними після завершення відповідних транзакцій, тоді як заморожені баланси залишаються недоступними для транзакцій, доки ви не вирішите їх розморозити.",
"unspent_change": "Зміна"
}
"unspent_change": "Зміна",
"Block_remaining": "${status} блок залишився",
"labeled_silent_addresses": "Позначені мовчазними адресами",
"use_testnet": "Використовуйте тестову мережу"
}

View file

@ -726,5 +726,8 @@
"seed_phrase_length": " ﯽﺋﺎﺒﻤﻟ ﯽﮐ ﮯﻠﻤﺟ ﮯﮐ ﺞﯿﺑ",
"unavailable_balance": " ﺲﻨﻠﯿﺑ ﺏﺎﯿﺘﺳﺩ ﺮﯿﻏ",
"unavailable_balance_description": "۔ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﮧﻠﺼﯿﻓ ﺎﮐ ﮯﻧﺮﮐ ﺪﻤﺠﻨﻣ ﻥﺍ ﮟﯿﮩﻧﺍ ﭖﺁ ﮧﮐ ﮏﺗ ﺐﺟ ﮟﯿﮨ ﮯﺘﮨﺭ ﯽﺋﺎﺳﺭ ﻞﺑﺎﻗﺎﻧ ﮏﺗ ﺖﻗﻭ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﻦﯾﺩ ﻦﯿﻟ ﺲﻨﻠﯿﺑ ﺪﻤﺠﻨﻣ ﮧﮐ ﺐﺟ ،ﮯﮔ ﮟﯿﺋﺎﺟ ﻮﮨ ﺏﺎﯿﺘﺳﺩ ﺲﻨﻠﯿﺑ ﻞﻔﻘﻣ ﺪﻌﺑ ﮯﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﻦﯾﺩ ﻦﯿﻟ ﮧﻘﻠﻌﺘﻣ ﮯﮐ ﻥﺍ ۔ﮯﮨ ﺎﮭﮐﺭ ﺮ",
"unspent_change": "تبدیل کریں"
"unspent_change": "تبدیل کریں",
"Block_remaining": "${status} باقی بلاک",
"labeled_silent_addresses": "خاموش پتے لیبل لگا",
"use_testnet": "ٹیسٹ نیٹ استعمال کریں"
}

View file

@ -728,5 +728,8 @@
"seed_phrase_length": "Gigun gbolohun irugbin",
"unavailable_balance": "Iwontunwonsi ti ko si",
"unavailable_balance_description": "Iwontunws.funfun ti ko si: Lapapọ yii pẹlu awọn owo ti o wa ni titiipa ni awọn iṣowo isunmọ ati awọn ti o ti didi ni itara ninu awọn eto iṣakoso owo rẹ. Awọn iwọntunwọnsi titiipa yoo wa ni kete ti awọn iṣowo oniwun wọn ba ti pari, lakoko ti awọn iwọntunwọnsi tio tutunini ko ni iraye si fun awọn iṣowo titi iwọ o fi pinnu lati mu wọn kuro.",
"unspent_change": "Yipada"
}
"unspent_change": "Yipada",
"Block_remaining": "${status} Bdund díẹ",
"labeled_silent_addresses": "Awọn adirẹsi ipalọlọ",
"use_testnet": "Lo tele"
}

View file

@ -733,5 +733,8 @@
"seed_phrase_length": "种子短语长度",
"unavailable_balance": "不可用余额",
"unavailable_balance_description": "不可用余额:此总额包括锁定在待处理交易中的资金以及您在硬币控制设置中主动冻结的资金。一旦各自的交易完成,锁定的余额将变得可用,而冻结的余额在您决定解冻之前仍然无法进行交易。",
"unspent_change": "改变"
}
"unspent_change": "改变",
"Block_remaining": "${status}块剩余",
"labeled_silent_addresses": "标记为无声地址",
"use_testnet": "使用TestNet"
}

16
scripts/android/shell.nix Normal file
View file

@ -0,0 +1,16 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.curl
pkgs.unzip
pkgs.automake
pkgs.file
pkgs.pkg-config
pkgs.git
pkgs.libtool
pkgs.ncurses5
pkgs.openjdk8
pkgs.clang
];
}

View file

@ -71,6 +71,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/litecoin_wallet_service.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as btc;
""";
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
const bitcoinContent = """
@ -87,12 +88,15 @@ abstract class Bitcoin {
TransactionPriority deserializeBitcoinTransactionPriority(int raw);
TransactionPriority deserializeLitecoinTransactionPriority(int raw);
int getFeeRate(Object wallet, TransactionPriority priority);
Future<void> generateNewAddress(Object wallet);
Future<void> generateNewAddress(Object wallet, {String? label});
Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate});
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority? priority, required int feeRate});
List<String> getAddresses(Object wallet);
String getAddress(Object wallet);
String getReceiveAddress(Object wallet);
btc.SilentPaymentAddress? getSilentAddress(Object wallet);
List<BitcoinAddressRecord> getSilentAddresses(Object wallet);
String formatterBitcoinAmountToString({required int amount});
double formatterBitcoinAmountToDouble({required int amount});