mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-18 08:45:05 +00:00
inital commit: testnet, silent payments
This commit is contained in:
parent
021e88e667
commit
4a3140035f
61 changed files with 826 additions and 457 deletions
|
@ -1,15 +1,17 @@
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
|
|
||||||
class BitcoinUnspent {
|
class BitcoinUnspent {
|
||||||
BitcoinUnspent(this.address, this.hash, this.value, this.vout)
|
BitcoinUnspent(this.address, this.hash, this.value, this.vout, {bool? isSilent})
|
||||||
: isSending = true,
|
: isSending = true,
|
||||||
isFrozen = false,
|
isFrozen = false,
|
||||||
note = '';
|
note = '',
|
||||||
|
isSilent = isSilent ?? false;
|
||||||
|
|
||||||
factory BitcoinUnspent.fromJSON(
|
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json,
|
||||||
BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
{bool? isSilent}) =>
|
||||||
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
|
BitcoinUnspent(
|
||||||
json['tx_pos'] as int);
|
address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int,
|
||||||
|
isSilent: isSilent);
|
||||||
|
|
||||||
final BitcoinAddressRecord address;
|
final BitcoinAddressRecord address;
|
||||||
final String hash;
|
final String hash;
|
||||||
|
@ -17,8 +19,12 @@ class BitcoinUnspent {
|
||||||
final int vout;
|
final int vout;
|
||||||
|
|
||||||
bool get isP2wpkh =>
|
bool get isP2wpkh =>
|
||||||
address.address.startsWith('bc') || address.address.startsWith('ltc');
|
address.address.startsWith('bc') ||
|
||||||
|
// testnet
|
||||||
|
address.address.startsWith('tb') ||
|
||||||
|
address.address.startsWith('ltc');
|
||||||
bool isSending;
|
bool isSending;
|
||||||
bool isFrozen;
|
bool isFrozen;
|
||||||
|
bool isSilent;
|
||||||
String note;
|
String note;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,77 +23,84 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
bitcoin.NetworkType? networkType,
|
||||||
required Uint8List seedBytes,
|
required Uint8List seedBytes,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
int initialRegularAddressIndex = 0,
|
int initialRegularAddressIndex = 0,
|
||||||
int initialChangeAddressIndex = 0})
|
int initialChangeAddressIndex = 0,
|
||||||
|
bitcoin.SilentPaymentAddress? silentAddress})
|
||||||
: super(
|
: super(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
networkType: bitcoin.bitcoin,
|
networkType: networkType ?? bitcoin.bitcoin,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
currency: CryptoCurrency.btc,
|
currency: CryptoCurrency.btc,
|
||||||
encryptionFileUtils: encryptionFileUtils) {
|
encryptionFileUtils: encryptionFileUtils) {
|
||||||
walletAddresses = BitcoinWalletAddresses(
|
walletAddresses = BitcoinWalletAddresses(walletInfo,
|
||||||
walletInfo,
|
|
||||||
electrumClient: electrumClient,
|
electrumClient: electrumClient,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
mainHd: hd,
|
mainHd: hd,
|
||||||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
|
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
|
||||||
.derivePath("m/0'/1"),
|
networkType: networkType ?? bitcoin.bitcoin,
|
||||||
networkType: networkType);
|
silentAddress: silentAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<BitcoinWallet> create({
|
static Future<BitcoinWallet> create(
|
||||||
required String mnemonic,
|
{required String mnemonic,
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
bitcoin.NetworkType? networkType,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
ElectrumBalance? initialBalance,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
int initialRegularAddressIndex = 0,
|
ElectrumBalance? initialBalance,
|
||||||
int initialChangeAddressIndex = 0
|
int initialRegularAddressIndex = 0,
|
||||||
}) async {
|
int initialChangeAddressIndex = 0}) async {
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
|
networkType: networkType,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
encryptionFileUtils: encryptionFileUtils,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex);
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
silentAddress: await bitcoin.SilentPaymentAddress.fromMnemonic(mnemonic,
|
||||||
|
hrp: networkType == bitcoin.bitcoin ? 'sp' : 'tsp'));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<BitcoinWallet> open({
|
static Future<BitcoinWallet> open(
|
||||||
required String name,
|
{required String name,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
required EncryptionFileUtils encryptionFileUtils
|
required EncryptionFileUtils encryptionFileUtils}) async {
|
||||||
}) async {
|
final snp =
|
||||||
final snp = await ElectrumWallletSnapshot.load(encryptionFileUtils, name, walletInfo.type, password);
|
await ElectrumWallletSnapshot.load(encryptionFileUtils, name, walletInfo.type, password);
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
mnemonic: snp.mnemonic,
|
mnemonic: snp.mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
|
networkType: snp.networkType,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp.addresses,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp.balance,
|
||||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
||||||
encryptionFileUtils: encryptionFileUtils,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex);
|
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||||
|
silentAddress: await bitcoin.SilentPaymentAddress.fromMnemonic(snp.mnemonic,
|
||||||
|
hrp: snp.networkType == bitcoin.bitcoin ? 'sp' : 'tsp'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,36 +4,34 @@ import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'bitcoin_wallet_addresses.g.dart';
|
part 'bitcoin_wallet_addresses.g.dart';
|
||||||
|
|
||||||
class BitcoinWalletAddresses = BitcoinWalletAddressesBase
|
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
|
||||||
with _$BitcoinWalletAddresses;
|
|
||||||
|
|
||||||
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses
|
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||||
with Store {
|
BitcoinWalletAddressesBase(WalletInfo walletInfo,
|
||||||
BitcoinWalletAddressesBase(
|
|
||||||
WalletInfo walletInfo,
|
|
||||||
{required bitcoin.HDWallet mainHd,
|
{required bitcoin.HDWallet mainHd,
|
||||||
required bitcoin.HDWallet sideHd,
|
required bitcoin.HDWallet sideHd,
|
||||||
required bitcoin.NetworkType networkType,
|
required bitcoin.NetworkType networkType,
|
||||||
required ElectrumClient electrumClient,
|
required ElectrumClient electrumClient,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
int initialRegularAddressIndex = 0,
|
int initialRegularAddressIndex = 0,
|
||||||
int initialChangeAddressIndex = 0})
|
int initialChangeAddressIndex = 0,
|
||||||
: super(
|
bitcoin.SilentPaymentAddress? silentAddress})
|
||||||
walletInfo,
|
: super(walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
mainHd: mainHd,
|
mainHd: mainHd,
|
||||||
sideHd: sideHd,
|
sideHd: sideHd,
|
||||||
electrumClient: electrumClient,
|
electrumClient: electrumClient,
|
||||||
networkType: networkType);
|
networkType: networkType,
|
||||||
|
silentAddress: silentAddress);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
|
String getAddress({required int index, required bitcoin.HDWallet hd}) =>
|
||||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,10 @@ import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
|
||||||
class BitcoinWalletService extends WalletService<
|
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
||||||
BitcoinNewWalletCredentials,
|
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
|
||||||
BitcoinRestoreWalletFromWIFCredentials> {
|
|
||||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect);
|
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
@ -27,12 +26,13 @@ class BitcoinWalletService extends WalletService<
|
||||||
WalletType getType() => WalletType.bitcoin;
|
WalletType getType() => WalletType.bitcoin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
final wallet = await BitcoinWalletBase.create(
|
final wallet = await BitcoinWalletBase.create(
|
||||||
mnemonic: await generateMnemonic(),
|
mnemonic: await generateMnemonic(),
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
networkType: isTestnet == true ? bitcoin.testnet : bitcoin.bitcoin,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -45,8 +45,8 @@ class BitcoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> openWallet(String name, String password) async {
|
Future<BitcoinWallet> openWallet(String name, String password) async {
|
||||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
final walletInfo = walletInfoSource.values
|
||||||
(info) => info.id == WalletBase.idFor(name, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||||
final wallet = await BitcoinWalletBase.open(
|
final wallet = await BitcoinWalletBase.open(
|
||||||
password: password,
|
password: password,
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -59,17 +59,16 @@ class BitcoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> remove(String wallet) async {
|
Future<void> remove(String wallet) async {
|
||||||
File(await pathForWalletDir(name: wallet, type: getType()))
|
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||||
.delete(recursive: true);
|
final walletInfo = walletInfoSource.values
|
||||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||||
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
|
||||||
await walletInfoSource.delete(walletInfo.key);
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> rename(String currentName, String password, String newName) async {
|
Future<void> rename(String currentName, String password, String newName) async {
|
||||||
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
|
final currentWalletInfo = walletInfoSource.values
|
||||||
(info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||||
final currentWallet = await BitcoinWalletBase.open(
|
final currentWallet = await BitcoinWalletBase.open(
|
||||||
password: password,
|
password: password,
|
||||||
name: currentName,
|
name: currentName,
|
||||||
|
@ -87,13 +86,11 @@ class BitcoinWalletService extends WalletService<
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromKeys(
|
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
||||||
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromSeed(
|
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
||||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
|
||||||
if (!validateMnemonic(credentials.mnemonic)) {
|
if (!validateMnemonic(credentials.mnemonic)) {
|
||||||
throw BitcoinMnemonicIsIncorrectException();
|
throw BitcoinMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,7 @@ String jsonrpc(
|
||||||
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
|
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
|
||||||
|
|
||||||
class SocketTask {
|
class SocketTask {
|
||||||
SocketTask({
|
SocketTask({required this.isSubscription, this.completer, this.subject});
|
||||||
required this.isSubscription,
|
|
||||||
this.completer,
|
|
||||||
this.subject});
|
|
||||||
|
|
||||||
final Completer<dynamic>? completer;
|
final Completer<dynamic>? completer;
|
||||||
final BehaviorSubject<dynamic>? subject;
|
final BehaviorSubject<dynamic>? subject;
|
||||||
|
@ -51,8 +48,7 @@ class ElectrumClient {
|
||||||
Timer? _aliveTimer;
|
Timer? _aliveTimer;
|
||||||
String unterminatedString;
|
String unterminatedString;
|
||||||
|
|
||||||
Future<void> connectToUri(Uri uri) async =>
|
Future<void> connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port);
|
||||||
await connect(host: uri.host, port: uri.port);
|
|
||||||
|
|
||||||
Future<void> connect({required String host, required int port}) async {
|
Future<void> connect({required String host, required int port}) async {
|
||||||
try {
|
try {
|
||||||
|
@ -104,21 +100,20 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isJSONStringCorrect(unterminatedString)) {
|
if (isJSONStringCorrect(unterminatedString)) {
|
||||||
final response =
|
final response = json.decode(unterminatedString) as Map<String, dynamic>;
|
||||||
json.decode(unterminatedString) as Map<String, dynamic>;
|
|
||||||
_handleResponse(response);
|
_handleResponse(response);
|
||||||
unterminatedString = '';
|
unterminatedString = '';
|
||||||
}
|
}
|
||||||
} on TypeError catch (e) {
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
unterminatedString += message;
|
unterminatedString += message;
|
||||||
|
|
||||||
if (isJSONStringCorrect(unterminatedString)) {
|
if (isJSONStringCorrect(unterminatedString)) {
|
||||||
final response =
|
final response = json.decode(unterminatedString) as Map<String, dynamic>;
|
||||||
json.decode(unterminatedString) as Map<String, dynamic>;
|
|
||||||
_handleResponse(response);
|
_handleResponse(response);
|
||||||
// unterminatedString = null;
|
// unterminatedString = null;
|
||||||
unterminatedString = '';
|
unterminatedString = '';
|
||||||
|
@ -142,8 +137,7 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> version() =>
|
Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
|
||||||
call(method: 'server.version').then((dynamic result) {
|
|
||||||
if (result is List) {
|
if (result is List) {
|
||||||
return result.map((dynamic val) => val.toString()).toList();
|
return result.map((dynamic val) => val.toString()).toList();
|
||||||
}
|
}
|
||||||
|
@ -180,9 +174,8 @@ class ElectrumClient {
|
||||||
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
|
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
|
||||||
String address, NetworkType networkType) =>
|
String address, NetworkType networkType) =>
|
||||||
call(
|
call(
|
||||||
method: 'blockchain.scripthash.listunspent',
|
method: 'blockchain.scripthash.listunspent',
|
||||||
params: [scriptHash(address, networkType: networkType)])
|
params: [scriptHash(address, networkType: networkType)]).then((dynamic result) {
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is List) {
|
if (result is List) {
|
||||||
return result.map((dynamic val) {
|
return result.map((dynamic val) {
|
||||||
if (val is Map<String, dynamic>) {
|
if (val is Map<String, dynamic>) {
|
||||||
|
@ -229,19 +222,25 @@ class ElectrumClient {
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getTransactionRaw(
|
Future<dynamic> getTransactionRaw(
|
||||||
{required String hash}) async =>
|
{required String hash, required NetworkType networkType}) async =>
|
||||||
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
|
callWithTimeout(
|
||||||
|
method: 'blockchain.transaction.get',
|
||||||
|
params: networkType == bitcoin ? [hash, true] : [hash],
|
||||||
|
timeout: 10000)
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is Map<String, dynamic>) {
|
if (result is Map<String, dynamic>) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (networkType == testnet && result is String) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
return <String, dynamic>{};
|
return <String, dynamic>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<String> getTransactionHex(
|
Future<String> getTransactionHex({required String hash}) async =>
|
||||||
{required String hash}) async =>
|
|
||||||
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
|
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is String) {
|
if (result is String) {
|
||||||
|
@ -251,8 +250,7 @@ class ElectrumClient {
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<String> broadcastTransaction(
|
Future<String> broadcastTransaction({required String transactionRaw}) async =>
|
||||||
{required String transactionRaw}) async =>
|
|
||||||
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
|
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
|
||||||
.then((dynamic result) {
|
.then((dynamic result) {
|
||||||
if (result is String) {
|
if (result is String) {
|
||||||
|
@ -262,19 +260,15 @@ class ElectrumClient {
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getMerkle(
|
Future<Map<String, dynamic>> getMerkle({required String hash, required int height}) async =>
|
||||||
{required String hash, required int height}) async =>
|
await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
|
||||||
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>;
|
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}) =>
|
Future<double> estimatefee({required int p}) =>
|
||||||
call(method: 'blockchain.estimatefee', params: [p])
|
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is double) {
|
if (result is double) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -319,15 +313,9 @@ class ElectrumClient {
|
||||||
final topDoubleString = await estimatefee(p: 1);
|
final topDoubleString = await estimatefee(p: 1);
|
||||||
final middleDoubleString = await estimatefee(p: 5);
|
final middleDoubleString = await estimatefee(p: 5);
|
||||||
final bottomDoubleString = await estimatefee(p: 100);
|
final bottomDoubleString = await estimatefee(p: 100);
|
||||||
final top =
|
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
|
||||||
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
|
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
||||||
.round();
|
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
||||||
final middle =
|
|
||||||
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
|
|
||||||
.round();
|
|
||||||
final bottom =
|
|
||||||
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
|
|
||||||
.round();
|
|
||||||
|
|
||||||
return [bottom, middle, top];
|
return [bottom, middle, top];
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
@ -344,16 +332,14 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
BehaviorSubject<T>? subscribe<T>(
|
BehaviorSubject<T>? subscribe<T>(
|
||||||
{required String id,
|
{required String id, required String method, List<Object> params = const []}) {
|
||||||
required String method,
|
|
||||||
List<Object> params = const []}) {
|
|
||||||
try {
|
try {
|
||||||
final subscription = BehaviorSubject<T>();
|
final subscription = BehaviorSubject<T>();
|
||||||
_regisrySubscription(id, subscription);
|
_regisrySubscription(id, subscription);
|
||||||
socket!.write(jsonrpc(method: method, id: _id, params: params));
|
socket!.write(jsonrpc(method: method, id: _id, params: params));
|
||||||
|
|
||||||
return subscription;
|
return subscription;
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -370,9 +356,7 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> callWithTimeout(
|
Future<dynamic> callWithTimeout(
|
||||||
{required String method,
|
{required String method, List<Object> params = const [], int timeout = 4000}) async {
|
||||||
List<Object> params = const [],
|
|
||||||
int timeout = 4000}) async {
|
|
||||||
try {
|
try {
|
||||||
final completer = Completer<dynamic>();
|
final completer = Completer<dynamic>();
|
||||||
_id += 1;
|
_id += 1;
|
||||||
|
@ -386,7 +370,7 @@ class ElectrumClient {
|
||||||
});
|
});
|
||||||
|
|
||||||
return completer.future;
|
return completer.future;
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,8 +381,8 @@ class ElectrumClient {
|
||||||
onConnectionStatusChange = null;
|
onConnectionStatusChange = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _registryTask(int id, Completer<dynamic> completer) => _tasks[id.toString()] =
|
void _registryTask(int id, Completer<dynamic> completer) =>
|
||||||
SocketTask(completer: completer, isSubscription: false);
|
_tasks[id.toString()] = SocketTask(completer: completer, isSubscription: false);
|
||||||
|
|
||||||
void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
|
void _regisrySubscription(String id, BehaviorSubject<dynamic> subject) =>
|
||||||
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
|
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
|
||||||
|
@ -419,8 +403,7 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _methodHandler(
|
void _methodHandler({required String method, required Map<String, dynamic> request}) {
|
||||||
{required String method, required Map<String, dynamic> request}) {
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'blockchain.scripthash.subscribe':
|
case 'blockchain.scripthash.subscribe':
|
||||||
final params = request['params'] as List<dynamic>;
|
final params = request['params'] as List<dynamic>;
|
||||||
|
@ -451,8 +434,8 @@ class ElectrumClient {
|
||||||
_methodHandler(method: method, request: response);
|
_methodHandler(method: method, request: response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != null){
|
if (id != null) {
|
||||||
_finish(id, result);
|
_finish(id, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,13 +35,15 @@ import 'package:cw_bitcoin/electrum.dart';
|
||||||
import 'package:hex/hex.dart';
|
import 'package:hex/hex.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
part 'electrum_wallet.g.dart';
|
part 'electrum_wallet.g.dart';
|
||||||
|
|
||||||
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
|
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
|
||||||
|
|
||||||
abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
abstract class ElectrumWalletBase
|
||||||
ElectrumTransactionHistory, ElectrumTransactionInfo> with Store {
|
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
|
||||||
|
with Store {
|
||||||
ElectrumWalletBase(
|
ElectrumWalletBase(
|
||||||
{required String password,
|
{required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
|
@ -54,28 +56,25 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
ElectrumClient? electrumClient,
|
ElectrumClient? electrumClient,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
CryptoCurrency? currency})
|
CryptoCurrency? currency})
|
||||||
: hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
|
: hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/0"),
|
||||||
.derivePath("m/0'/0"),
|
|
||||||
syncStatus = NotConnectedSyncStatus(),
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
_password = password,
|
_password = password,
|
||||||
_feeRates = <int>[],
|
_feeRates = <int>[],
|
||||||
_isTransactionUpdating = false,
|
_isTransactionUpdating = false,
|
||||||
unspentCoins = [],
|
unspentCoins = [],
|
||||||
_scripthashesUpdateSubject = {},
|
_scripthashesUpdateSubject = {},
|
||||||
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(
|
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
|
||||||
currency != null
|
? {
|
||||||
? {currency: initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0,
|
currency:
|
||||||
frozen: 0)}
|
initialBalance ?? const ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0)
|
||||||
: {}),
|
}
|
||||||
|
: {}),
|
||||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
this.electrumClient = electrumClient ?? ElectrumClient();
|
this.electrumClient = electrumClient ?? ElectrumClient();
|
||||||
this.walletInfo = walletInfo;
|
this.walletInfo = walletInfo;
|
||||||
transactionHistory =
|
transactionHistory = ElectrumTransactionHistory(
|
||||||
ElectrumTransactionHistory(
|
walletInfo: walletInfo, password: password, encryptionFileUtils: encryptionFileUtils);
|
||||||
walletInfo: walletInfo,
|
|
||||||
password: password,
|
|
||||||
encryptionFileUtils: encryptionFileUtils);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
||||||
|
@ -104,9 +103,9 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
List<String> get publicScriptHashes => walletAddresses.addresses
|
List<String> get publicScriptHashes => walletAddresses.addresses
|
||||||
.where((addr) => !addr.isHidden)
|
.where((addr) => !addr.isHidden)
|
||||||
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
String get xpub => hd.base58!;
|
String get xpub => hd.base58!;
|
||||||
|
|
||||||
|
@ -119,8 +118,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
bitcoin.NetworkType networkType;
|
bitcoin.NetworkType networkType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BitcoinWalletKeys get keys => BitcoinWalletKeys(
|
BitcoinWalletKeys get keys =>
|
||||||
wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
|
BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
|
||||||
|
|
||||||
String _password;
|
String _password;
|
||||||
List<BitcoinUnspent> unspentCoins;
|
List<BitcoinUnspent> unspentCoins;
|
||||||
|
@ -148,8 +147,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
await updateBalance();
|
await updateBalance();
|
||||||
_feeRates = await electrumClient.feeRates();
|
_feeRates = await electrumClient.feeRates();
|
||||||
|
|
||||||
Timer.periodic(const Duration(minutes: 1),
|
Timer.periodic(
|
||||||
(timer) async => _feeRates = await electrumClient.feeRates());
|
const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
|
||||||
|
|
||||||
syncStatus = SyncedSyncStatus();
|
syncStatus = SyncedSyncStatus();
|
||||||
} catch (e, stacktrace) {
|
} catch (e, stacktrace) {
|
||||||
|
@ -178,8 +177,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PendingBitcoinTransaction> createTransaction(
|
Future<PendingBitcoinTransaction> createTransaction(Object credentials) async {
|
||||||
Object credentials) async {
|
|
||||||
const minAmount = 546;
|
const minAmount = 546;
|
||||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||||
final inputs = <BitcoinUnspent>[];
|
final inputs = <BitcoinUnspent>[];
|
||||||
|
@ -202,9 +200,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
throw BitcoinTransactionNoInputsException();
|
throw BitcoinTransactionNoInputsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
final allAmountFee = transactionCredentials.feeRate != null
|
final allAmountFee = 188;
|
||||||
? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length)
|
|
||||||
: feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length);
|
|
||||||
|
|
||||||
final allAmount = allInputsAmount - allAmountFee;
|
final allAmount = allInputsAmount - allAmountFee;
|
||||||
|
|
||||||
|
@ -213,13 +209,11 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
var fee = 0;
|
var fee = 0;
|
||||||
|
|
||||||
if (hasMultiDestination) {
|
if (hasMultiDestination) {
|
||||||
if (outputs.any((item) => item.sendAll
|
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
|
||||||
|| item.formattedCryptoAmount! <= 0)) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
credentialsAmount = outputs.fold(0, (acc, value) =>
|
credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
|
||||||
acc + value.formattedCryptoAmount!);
|
|
||||||
|
|
||||||
if (allAmount - credentialsAmount < minAmount) {
|
if (allAmount - credentialsAmount < minAmount) {
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
@ -236,9 +230,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final output = outputs.first;
|
final output = outputs.first;
|
||||||
credentialsAmount = !output.sendAll
|
credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
|
||||||
? output.formattedCryptoAmount!
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
if (credentialsAmount > allAmount) {
|
if (credentialsAmount > allAmount) {
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
|
@ -257,14 +249,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fee == 0) {
|
if (fee == 0 && networkType == bitcoin.bitcoin) {
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
// throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
final totalAmount = amount + fee;
|
final totalAmount = amount + fee;
|
||||||
|
|
||||||
if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
|
if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
// throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
final txb = bitcoin.TransactionBuilder(network: networkType);
|
final txb = bitcoin.TransactionBuilder(network: networkType);
|
||||||
|
@ -291,18 +283,26 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amount <= 0 || totalInputAmount < totalAmount) {
|
if (amount <= 0 || totalInputAmount < totalAmount) {
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
// throw BitcoinTransactionWrongBalanceException(currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
txb.setVersion(1);
|
txb.setVersion(1);
|
||||||
|
List<bitcoin.PrivateKeyInfo> privateKeys = [];
|
||||||
|
List<bitcoin.Outpoint> outpoints = [];
|
||||||
inputs.forEach((input) {
|
inputs.forEach((input) {
|
||||||
|
privateKeys.add(bitcoin.PrivateKeyInfo(
|
||||||
|
bitcoin.ECPrivateKey(Uint8List.fromList(
|
||||||
|
HEX.decode(walletAddresses.sideHd.derive(input.address.index).privKey!))),
|
||||||
|
false));
|
||||||
|
outpoints.add(bitcoin.Outpoint(Uint8List.fromList(HEX.decode(input.hash)), input.vout));
|
||||||
|
|
||||||
if (input.isP2wpkh) {
|
if (input.isP2wpkh) {
|
||||||
final p2wpkh = bitcoin
|
final p2wpkh = bitcoin
|
||||||
.P2WPKH(
|
.P2WPKH(
|
||||||
data: generatePaymentData(
|
data: generatePaymentData(
|
||||||
hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
|
hd: input.address.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
|
||||||
index: input.address.index),
|
index: input.address.index),
|
||||||
network: networkType)
|
network: networkType)
|
||||||
.data;
|
.data;
|
||||||
|
|
||||||
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
|
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
|
||||||
|
@ -311,28 +311,43 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
List<bitcoin.SilentPaymentDestination> silentDestinations = [];
|
||||||
outputs.forEach((item) {
|
outputs.forEach((item) {
|
||||||
final outputAmount = hasMultiDestination
|
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
|
||||||
? item.formattedCryptoAmount
|
final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
|
||||||
: amount;
|
if (outputAddress.startsWith('tsp1')) {
|
||||||
final outputAddress = item.isParsedAddress
|
silentDestinations
|
||||||
? item.extractedAddress!
|
.add(bitcoin.SilentPaymentDestination.fromAddress(outputAddress, outputAmount!));
|
||||||
: item.address;
|
} else {
|
||||||
txb.addOutput(
|
txb.addOutput(addressToOutputScript(outputAddress, networkType), outputAmount!);
|
||||||
addressToOutputScript(outputAddress, networkType),
|
}
|
||||||
outputAmount!);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final estimatedSize =
|
if (silentDestinations.isNotEmpty) {
|
||||||
estimatedTransactionSize(inputs.length, outputs.length + 1);
|
final outpointsHash = bitcoin.SilentPayment.hashOutpoints(outpoints);
|
||||||
var feeAmount = 0;
|
|
||||||
|
|
||||||
if (transactionCredentials.feeRate != null) {
|
final sumOfInputPrivKeys = bitcoin.getSumInputPrivKeys(privateKeys);
|
||||||
feeAmount = transactionCredentials.feeRate! * estimatedSize;
|
|
||||||
} else {
|
final generatedPubkeys = bitcoin.SilentPayment.generateMultipleRecipientPubkeys(
|
||||||
feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize;
|
sumOfInputPrivKeys, outpointsHash, silentDestinations);
|
||||||
|
|
||||||
|
generatedPubkeys.forEach((recipientSilentAddress, generatedOutputs) {
|
||||||
|
generatedOutputs.forEach((output) {
|
||||||
|
final generatedPubkey = HEX.encode(output.$1.data);
|
||||||
|
txb.addOutput(bitcoin.getTaproot(generatedPubkey).toScriptPubKey().toBytes(), output.$2);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final estimatedSize = estimatedTransactionSize(inputs.length, outputs.length + 1);
|
||||||
|
var feeAmount = 188;
|
||||||
|
|
||||||
|
// if (transactionCredentials.feeRate != null) {
|
||||||
|
// feeAmount = transactionCredentials.feeRate! * estimatedSize;
|
||||||
|
// } else {
|
||||||
|
// feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize;
|
||||||
|
// }
|
||||||
|
|
||||||
final changeValue = totalInputAmount - amount - feeAmount;
|
final changeValue = totalInputAmount - amount - feeAmount;
|
||||||
|
|
||||||
if (changeValue > minAmount) {
|
if (changeValue > minAmount) {
|
||||||
|
@ -363,7 +378,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
|
'account_index': walletAddresses.currentReceiveAddressIndex.toString(),
|
||||||
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
|
'change_address_index': walletAddresses.currentChangeAddressIndex.toString(),
|
||||||
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
|
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
|
||||||
'balance': balance[currency]?.toJSON()
|
'balance': balance[currency]?.toJSON(),
|
||||||
|
'network_type': networkType.toString()
|
||||||
});
|
});
|
||||||
|
|
||||||
int feeRate(TransactionPriority priority) {
|
int feeRate(TransactionPriority priority) {
|
||||||
|
@ -373,34 +389,29 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
} catch(_) {
|
} catch (_) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
|
int feeAmountForPriority(
|
||||||
int outputsCount) =>
|
BitcoinTransactionPriority priority, int inputsCount, int outputsCount) =>
|
||||||
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
|
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
|
||||||
|
|
||||||
int feeAmountWithFeeRate(int feeRate, int inputsCount,
|
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount) =>
|
||||||
int outputsCount) =>
|
|
||||||
feeRate * estimatedTransactionSize(inputsCount, outputsCount);
|
feeRate * estimatedTransactionSize(inputsCount, outputsCount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int calculateEstimatedFee(TransactionPriority? priority, int? amount,
|
int calculateEstimatedFee(TransactionPriority? priority, int? amount, {int? outputsCount}) {
|
||||||
{int? outputsCount}) {
|
|
||||||
if (priority is BitcoinTransactionPriority) {
|
if (priority is BitcoinTransactionPriority) {
|
||||||
return calculateEstimatedFeeWithFeeRate(
|
return calculateEstimatedFeeWithFeeRate(feeRate(priority), amount,
|
||||||
feeRate(priority),
|
outputsCount: outputsCount);
|
||||||
amount,
|
|
||||||
outputsCount: outputsCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount,
|
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount}) {
|
||||||
{int? outputsCount}) {
|
|
||||||
int inputsCount = 0;
|
int inputsCount = 0;
|
||||||
|
|
||||||
if (amount != null) {
|
if (amount != null) {
|
||||||
|
@ -429,8 +440,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
// If send all, then we have no change value
|
// If send all, then we have no change value
|
||||||
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
|
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
|
||||||
|
|
||||||
return feeAmountWithFeeRate(
|
return feeAmountWithFeeRate(feeRate, inputsCount, _outputsCount);
|
||||||
feeRate, inputsCount, _outputsCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -445,8 +455,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||||
final currentWalletFile = File(currentWalletPath);
|
final currentWalletFile = File(currentWalletPath);
|
||||||
|
|
||||||
final currentDirPath =
|
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||||
await pathForWalletDir(name: walletInfo.name, type: type);
|
|
||||||
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
|
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
|
||||||
|
|
||||||
// Copies current wallet files into new wallet name's dir and files
|
// Copies current wallet files into new wallet name's dir and files
|
||||||
|
@ -483,21 +492,63 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> makePath() async =>
|
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
|
|
||||||
Future<void> updateUnspent() async {
|
Future<void> updateUnspent() async {
|
||||||
final unspent = await Future.wait(walletAddresses
|
final unspent = await Future.wait(walletAddresses.addresses.map((address) => electrumClient
|
||||||
.addresses.map((address) => electrumClient
|
|
||||||
.getListUnspentWithAddress(address.address, networkType)
|
.getListUnspentWithAddress(address.address, networkType)
|
||||||
.then((unspent) => unspent
|
.then((unspent) => unspent.map((unspent) {
|
||||||
.map((unspent) {
|
|
||||||
try {
|
try {
|
||||||
return BitcoinUnspent.fromJSON(address, unspent);
|
return BitcoinUnspent.fromJSON(address, unspent);
|
||||||
} catch(_) {
|
} catch (_) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).whereNotNull())));
|
}).whereNotNull())));
|
||||||
|
final uri = Uri(
|
||||||
|
scheme: 'https',
|
||||||
|
host: 'blockstream.info',
|
||||||
|
path: '/testnet/api/tx/986547a4daec37b21d2252e39c740d77ff92d927343b0b6e017d45e857955efa');
|
||||||
|
|
||||||
|
await http.get(uri).then((response) {
|
||||||
|
final obj = json.decode(response.body);
|
||||||
|
final scanPrivateKey = walletAddresses.silentAddress!.scanPrivkey;
|
||||||
|
final spendPublicKey = walletAddresses.silentAddress!.spendPubkey;
|
||||||
|
Uint8List? sumOfInputPublicKeys;
|
||||||
|
List<bitcoin.Outpoint> outpoints = [];
|
||||||
|
obj["vin"].forEach((input) {
|
||||||
|
sumOfInputPublicKeys = Uint8List.fromList(HEX.decode(input["witness"][1] as String));
|
||||||
|
outpoints.add(bitcoin.Outpoint(
|
||||||
|
Uint8List.fromList(HEX.decode(input['txid'] as String)), input['vout'] as int));
|
||||||
|
});
|
||||||
|
final outpointHash = bitcoin.SilentPayment.hashOutpoints(outpoints);
|
||||||
|
List<Uint8List> outputs = [];
|
||||||
|
obj['vout'].forEach((out) {
|
||||||
|
outputs.add(Uint8List.fromList(
|
||||||
|
HEX.decode(bitcoin.getScript(out["scriptpubkey"] as String)[1] as String)));
|
||||||
|
});
|
||||||
|
final result = bitcoin.scanOutputs(
|
||||||
|
scanPrivateKey.data, spendPublicKey.data, sumOfInputPublicKeys!, outpointHash, outputs);
|
||||||
|
result.forEach((key, value) {
|
||||||
|
final tweak = value;
|
||||||
|
// TODO: store tweak for BitcoinUnspent
|
||||||
|
final spendPrivateKey = walletAddresses.silentAddress!.spendPrivkey;
|
||||||
|
final privKey = spendPrivateKey.tweak(tweak);
|
||||||
|
final pubKey = bitcoin.ECPrivateKey(privKey!.data).pubkey;
|
||||||
|
int i = 0;
|
||||||
|
final vout = obj['vout'].firstWhere((out) {
|
||||||
|
final script = bitcoin.getScript(out["scriptpubkey"] as String);
|
||||||
|
final scriptHash = script[1] as String;
|
||||||
|
i++;
|
||||||
|
return scriptHash == key;
|
||||||
|
});
|
||||||
|
unspent.add([
|
||||||
|
BitcoinUnspent.fromJSON(
|
||||||
|
BitcoinAddressRecord(vout["scriptpubkey_address"] as String, index: 0, isUsed: true),
|
||||||
|
{"tx_hash": obj["txid"], "value": vout["value"], "tx_pos": i},
|
||||||
|
isSilent: true)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
unspentCoins = unspent.expand((e) => e).toList();
|
unspentCoins = unspent.expand((e) => e).toList();
|
||||||
|
|
||||||
if (unspentCoinsInfo.isEmpty) {
|
if (unspentCoinsInfo.isEmpty) {
|
||||||
|
@ -508,7 +559,9 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
if (unspentCoins.isNotEmpty) {
|
if (unspentCoins.isNotEmpty) {
|
||||||
unspentCoins.forEach((coin) {
|
unspentCoins.forEach((coin) {
|
||||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||||
element.walletId.contains(id) && element.hash.contains(coin.hash));
|
element.walletId.contains(id) &&
|
||||||
|
element.hash.contains(coin.hash) &&
|
||||||
|
element.address.contains(coin.address.address));
|
||||||
|
|
||||||
if (coinInfoList.isNotEmpty) {
|
if (coinInfoList.isNotEmpty) {
|
||||||
final coinInfo = coinInfoList.first;
|
final coinInfo = coinInfoList.first;
|
||||||
|
@ -527,14 +580,14 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
|
|
||||||
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
|
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
|
||||||
final newInfo = UnspentCoinsInfo(
|
final newInfo = UnspentCoinsInfo(
|
||||||
walletId: id,
|
walletId: id,
|
||||||
hash: coin.hash,
|
hash: coin.hash,
|
||||||
isFrozen: coin.isFrozen,
|
isFrozen: coin.isFrozen,
|
||||||
isSending: coin.isSending,
|
isSending: coin.isSending,
|
||||||
noteRaw: coin.note,
|
noteRaw: coin.note,
|
||||||
address: coin.address.address,
|
address: coin.address.address,
|
||||||
value: coin.value,
|
value: coin.value,
|
||||||
vout: coin.vout,
|
vout: coin.vout,
|
||||||
);
|
);
|
||||||
|
|
||||||
await unspentCoinsInfo.add(newInfo);
|
await unspentCoinsInfo.add(newInfo);
|
||||||
|
@ -543,8 +596,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
Future<void> _refreshUnspentCoinsInfo() async {
|
Future<void> _refreshUnspentCoinsInfo() async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> keys = <dynamic>[];
|
final List<dynamic> keys = <dynamic>[];
|
||||||
final currentWalletUnspentCoins = unspentCoinsInfo.values
|
final currentWalletUnspentCoins =
|
||||||
.where((element) => element.walletId.contains(id));
|
unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
|
||||||
|
|
||||||
if (currentWalletUnspentCoins.isNotEmpty) {
|
if (currentWalletUnspentCoins.isNotEmpty) {
|
||||||
currentWalletUnspentCoins.forEach((element) {
|
currentWalletUnspentCoins.forEach((element) {
|
||||||
|
@ -566,12 +619,23 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
|
|
||||||
Future<ElectrumTransactionBundle> getTransactionExpanded(
|
Future<ElectrumTransactionBundle> getTransactionExpanded(
|
||||||
{required String hash, required int height}) async {
|
{required String hash, required int height}) async {
|
||||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
final verboseTransaction =
|
||||||
final transactionHex = verboseTransaction['hex'] as String;
|
await electrumClient.getTransactionRaw(hash: hash, networkType: networkType);
|
||||||
|
|
||||||
|
String transactionHex;
|
||||||
|
int? time;
|
||||||
|
int confirmations = 0;
|
||||||
|
if (networkType == bitcoin.testnet) {
|
||||||
|
transactionHex = verboseTransaction as String;
|
||||||
|
confirmations = 1;
|
||||||
|
} else {
|
||||||
|
transactionHex = verboseTransaction['hex'] as String;
|
||||||
|
time = verboseTransaction['time'] as int?;
|
||||||
|
confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
final original = bitcoin.Transaction.fromHex(transactionHex);
|
final original = bitcoin.Transaction.fromHex(transactionHex);
|
||||||
final ins = <bitcoin.Transaction>[];
|
final ins = <bitcoin.Transaction>[];
|
||||||
final time = verboseTransaction['time'] as int?;
|
|
||||||
final confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
|
||||||
|
|
||||||
for (final vin in original.ins) {
|
for (final vin in original.ins) {
|
||||||
final id = HEX.encode(vin.hash!.reversed.toList());
|
final id = HEX.encode(vin.hash!.reversed.toList());
|
||||||
|
@ -580,27 +644,19 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
ins.add(tx);
|
ins.add(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ElectrumTransactionBundle(
|
return ElectrumTransactionBundle(original, ins: ins, time: time, confirmations: confirmations);
|
||||||
original,
|
|
||||||
ins: ins,
|
|
||||||
time: time,
|
|
||||||
confirmations: confirmations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
||||||
{required String hash, required int height}) async {
|
{required String hash, required int height}) async {
|
||||||
try {
|
try {
|
||||||
final tx = await getTransactionExpanded(hash: hash, height: height);
|
final tx = await getTransactionExpanded(hash: hash, height: height);
|
||||||
final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
|
final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet();
|
||||||
return ElectrumTransactionInfo.fromElectrumBundle(
|
return ElectrumTransactionInfo.fromElectrumBundle(tx, walletInfo.type, networkType,
|
||||||
tx,
|
addresses: addresses, height: height);
|
||||||
walletInfo.type,
|
} catch (_) {
|
||||||
networkType,
|
return null;
|
||||||
addresses: addresses,
|
}
|
||||||
height: height);
|
|
||||||
} catch(_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -611,10 +667,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
||||||
addressHashes[sh] = addressRecord;
|
addressHashes[sh] = addressRecord;
|
||||||
});
|
});
|
||||||
final histories =
|
final histories = addressHashes.keys.map((scriptHash) =>
|
||||||
addressHashes.keys.map((scriptHash) => electrumClient
|
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
|
||||||
.getHistory(scriptHash)
|
|
||||||
.then((history) => {scriptHash: history}));
|
|
||||||
final historyResults = await Future.wait(histories);
|
final historyResults = await Future.wait(histories);
|
||||||
historyResults.forEach((history) {
|
historyResults.forEach((history) {
|
||||||
history.entries.forEach((historyItem) {
|
history.entries.forEach((historyItem) {
|
||||||
|
@ -625,19 +679,16 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
final historiesWithDetails = await Future.wait(
|
final historiesWithDetails = await Future.wait(normalizedHistories.map((transaction) {
|
||||||
normalizedHistories
|
try {
|
||||||
.map((transaction) {
|
return fetchTransactionInfo(
|
||||||
try {
|
hash: transaction['tx_hash'] as String, height: transaction['height'] as int);
|
||||||
return fetchTransactionInfo(
|
} catch (_) {
|
||||||
hash: transaction['tx_hash'] as String,
|
return Future.value(null);
|
||||||
height: transaction['height'] as int);
|
}
|
||||||
} catch(_) {
|
}));
|
||||||
return Future.value(null);
|
return historiesWithDetails
|
||||||
}
|
.fold<Map<String, ElectrumTransactionInfo>>(<String, ElectrumTransactionInfo>{}, (acc, tx) {
|
||||||
}));
|
|
||||||
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
|
|
||||||
<String, ElectrumTransactionInfo>{}, (acc, tx) {
|
|
||||||
if (tx == null) {
|
if (tx == null) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
@ -690,6 +741,9 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
final addresses = walletAddresses.addresses.toList();
|
final addresses = walletAddresses.addresses.toList();
|
||||||
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
||||||
|
|
||||||
|
var totalConfirmed = 0;
|
||||||
|
var totalUnconfirmed = 0;
|
||||||
|
|
||||||
for (var i = 0; i < addresses.length; i++) {
|
for (var i = 0; i < addresses.length; i++) {
|
||||||
final addressRecord = addresses[i];
|
final addressRecord = addresses[i];
|
||||||
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
final sh = scriptHash(addressRecord.address, networkType: networkType);
|
||||||
|
@ -700,16 +754,21 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
var totalFrozen = 0;
|
var totalFrozen = 0;
|
||||||
unspentCoinsInfo.values.forEach((info) {
|
unspentCoinsInfo.values.forEach((info) {
|
||||||
unspentCoins.forEach((element) {
|
unspentCoins.forEach((element) {
|
||||||
if (element.hash == info.hash && info.isFrozen && element.address.address == info.address
|
if (element.hash == info.hash &&
|
||||||
&& element.value == info.value) {
|
info.isFrozen &&
|
||||||
|
element.address.address == info.address &&
|
||||||
|
element.value == info.value) {
|
||||||
totalFrozen += element.value;
|
totalFrozen += element.value;
|
||||||
|
} else if (element.hash == info.hash &&
|
||||||
|
element.isSilent &&
|
||||||
|
element.address.address == info.address &&
|
||||||
|
element.value == info.value) {
|
||||||
|
totalConfirmed += element.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
final balances = await Future.wait(balanceFutures);
|
final balances = await Future.wait(balanceFutures);
|
||||||
var totalConfirmed = 0;
|
|
||||||
var totalUnconfirmed = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < balances.length; i++) {
|
for (var i = 0; i < balances.length; i++) {
|
||||||
final addressRecord = addresses[i];
|
final addressRecord = addresses[i];
|
||||||
|
@ -724,8 +783,8 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ElectrumBalance(confirmed: totalConfirmed, unconfirmed: totalUnconfirmed,
|
return ElectrumBalance(
|
||||||
frozen: totalFrozen);
|
confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateBalance() async {
|
Future<void> updateBalance() async {
|
||||||
|
@ -736,9 +795,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
||||||
String getChangeAddress() {
|
String getChangeAddress() {
|
||||||
const minCountOfHiddenAddresses = 5;
|
const minCountOfHiddenAddresses = 5;
|
||||||
final random = Random();
|
final random = Random();
|
||||||
var addresses = walletAddresses.addresses
|
var addresses = walletAddresses.addresses.where((addr) => addr.isHidden).toList();
|
||||||
.where((addr) => addr.isHidden)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (addresses.length < minCountOfHiddenAddresses) {
|
if (addresses.length < minCountOfHiddenAddresses) {
|
||||||
addresses = walletAddresses.addresses.toList();
|
addresses = walletAddresses.addresses.toList();
|
||||||
|
|
|
@ -8,8 +8,7 @@ import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'electrum_wallet_addresses.g.dart';
|
part 'electrum_wallet_addresses.g.dart';
|
||||||
|
|
||||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase
|
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
|
||||||
with _$ElectrumWalletAddresses;
|
|
||||||
|
|
||||||
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
||||||
|
@ -19,20 +18,19 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
required this.networkType,
|
required this.networkType,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
int initialRegularAddressIndex = 0,
|
int initialRegularAddressIndex = 0,
|
||||||
int initialChangeAddressIndex = 0})
|
int initialChangeAddressIndex = 0,
|
||||||
: addresses = ObservableList<BitcoinAddressRecord>.of(
|
bitcoin.SilentPaymentAddress? silentAddress})
|
||||||
(initialAddresses ?? []).toSet()),
|
: addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
|
silentAddress = silentAddress,
|
||||||
(initialAddresses ?? [])
|
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
||||||
.toSet()),
|
.toSet()),
|
||||||
changeAddresses = ObservableList<BitcoinAddressRecord>.of(
|
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||||
(initialAddresses ?? [])
|
|
||||||
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
||||||
.toSet()),
|
.toSet()),
|
||||||
currentReceiveAddressIndex = initialRegularAddressIndex,
|
currentReceiveAddressIndex = initialRegularAddressIndex,
|
||||||
currentChangeAddressIndex = initialChangeAddressIndex,
|
currentChangeAddressIndex = initialChangeAddressIndex,
|
||||||
super(walletInfo);
|
super(walletInfo);
|
||||||
|
|
||||||
static const defaultReceiveAddressesCount = 22;
|
static const defaultReceiveAddressesCount = 22;
|
||||||
static const defaultChangeAddressesCount = 17;
|
static const defaultChangeAddressesCount = 17;
|
||||||
|
@ -46,9 +44,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
final bitcoin.HDWallet mainHd;
|
final bitcoin.HDWallet mainHd;
|
||||||
final bitcoin.HDWallet sideHd;
|
final bitcoin.HDWallet sideHd;
|
||||||
|
|
||||||
@override
|
// TODO: labels -> disable edit on receive page
|
||||||
|
final bitcoin.SilentPaymentAddress? silentAddress;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String? activeAddress;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
String get address {
|
String get receiveAddress {
|
||||||
if (receiveAddresses.isEmpty) {
|
if (receiveAddresses.isEmpty) {
|
||||||
return generateNewAddress().address;
|
return generateNewAddress().address;
|
||||||
}
|
}
|
||||||
|
@ -57,28 +60,40 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set address(String addr) => null;
|
@computed
|
||||||
|
String get address {
|
||||||
|
if (activeAddress != null) {
|
||||||
|
return activeAddress!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receiveAddresses.isEmpty) {
|
||||||
|
return generateNewAddress().address;
|
||||||
|
}
|
||||||
|
|
||||||
|
return receiveAddresses.first.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set address(String addr) => activeAddress = addr;
|
||||||
|
|
||||||
int currentReceiveAddressIndex;
|
int currentReceiveAddressIndex;
|
||||||
int currentChangeAddressIndex;
|
int currentChangeAddressIndex;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
int get totalCountOfReceiveAddresses =>
|
int get totalCountOfReceiveAddresses => addresses.fold(0, (acc, addressRecord) {
|
||||||
addresses.fold(0, (acc, addressRecord) {
|
if (!addressRecord.isHidden) {
|
||||||
if (!addressRecord.isHidden) {
|
return acc + 1;
|
||||||
return acc + 1;
|
}
|
||||||
}
|
return acc;
|
||||||
return acc;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
int get totalCountOfChangeAddresses =>
|
int get totalCountOfChangeAddresses => addresses.fold(0, (acc, addressRecord) {
|
||||||
addresses.fold(0, (acc, addressRecord) {
|
if (addressRecord.isHidden) {
|
||||||
if (addressRecord.isHidden) {
|
return acc + 1;
|
||||||
return acc + 1;
|
}
|
||||||
}
|
return acc;
|
||||||
return acc;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
Future<void> discoverAddresses() async {
|
Future<void> discoverAddresses() async {
|
||||||
await _discoverAddresses(mainHd, false);
|
await _discoverAddresses(mainHd, false);
|
||||||
|
@ -105,15 +120,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@action
|
@action
|
||||||
Future<String> getChangeAddress() async {
|
Future<String> getChangeAddress() async {
|
||||||
updateChangeAddresses();
|
updateChangeAddresses();
|
||||||
|
|
||||||
if (changeAddresses.isEmpty) {
|
if (changeAddresses.isEmpty) {
|
||||||
final newAddresses = await _createNewAddresses(
|
final newAddresses = await _createNewAddresses(gap,
|
||||||
gap,
|
hd: sideHd,
|
||||||
hd: sideHd,
|
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
|
||||||
startIndex: totalCountOfChangeAddresses > 0
|
isHidden: true);
|
||||||
? totalCountOfChangeAddresses - 1
|
|
||||||
: 0,
|
|
||||||
isHidden: true);
|
|
||||||
_addAddresses(newAddresses);
|
_addAddresses(newAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,8 +139,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
BitcoinAddressRecord generateNewAddress(
|
BitcoinAddressRecord generateNewAddress({bitcoin.HDWallet? hd, bool isHidden = false}) {
|
||||||
{bitcoin.HDWallet? hd, bool isHidden = false}) {
|
|
||||||
currentReceiveAddressIndex += 1;
|
currentReceiveAddressIndex += 1;
|
||||||
// FIX-ME: Check logic for whichi HD should be used here ???
|
// FIX-ME: Check logic for whichi HD should be used here ???
|
||||||
final address = BitcoinAddressRecord(
|
final address = BitcoinAddressRecord(
|
||||||
|
@ -155,16 +166,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@action
|
@action
|
||||||
void updateReceiveAddresses() {
|
void updateReceiveAddresses() {
|
||||||
receiveAddresses.removeRange(0, receiveAddresses.length);
|
receiveAddresses.removeRange(0, receiveAddresses.length);
|
||||||
final newAdresses = addresses
|
final newAdresses =
|
||||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
||||||
receiveAddresses.addAll(newAdresses);
|
receiveAddresses.addAll(newAdresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void updateChangeAddresses() {
|
void updateChangeAddresses() {
|
||||||
changeAddresses.removeRange(0, changeAddresses.length);
|
changeAddresses.removeRange(0, changeAddresses.length);
|
||||||
final newAdresses = addresses
|
final newAdresses =
|
||||||
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
|
addresses.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed);
|
||||||
changeAddresses.addAll(newAdresses);
|
changeAddresses.addAll(newAdresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,20 +184,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
List<BitcoinAddressRecord> addrs;
|
List<BitcoinAddressRecord> addrs;
|
||||||
|
|
||||||
if (addresses.isNotEmpty) {
|
if (addresses.isNotEmpty) {
|
||||||
addrs = addresses
|
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
|
||||||
.where((addr) => addr.isHidden == isHidden)
|
|
||||||
.toList();
|
|
||||||
} else {
|
} else {
|
||||||
addrs = await _createNewAddresses(
|
addrs = await _createNewAddresses(
|
||||||
isHidden
|
isHidden ? defaultChangeAddressesCount : defaultReceiveAddressesCount,
|
||||||
? defaultChangeAddressesCount
|
|
||||||
: defaultReceiveAddressesCount,
|
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
hd: hd,
|
hd: hd,
|
||||||
isHidden: isHidden);
|
isHidden: isHidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
while(hasAddrUse) {
|
while (hasAddrUse) {
|
||||||
final addr = addrs.last.address;
|
final addr = addrs.last.address;
|
||||||
hasAddrUse = await _hasAddressUsed(addr);
|
hasAddrUse = await _hasAddressUsed(addr);
|
||||||
|
|
||||||
|
@ -196,11 +203,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
final start = addrs.length;
|
final start = addrs.length;
|
||||||
final count = start + gap;
|
final count = start + gap;
|
||||||
final batch = await _createNewAddresses(
|
final batch = await _createNewAddresses(count, startIndex: start, hd: hd, isHidden: isHidden);
|
||||||
count,
|
|
||||||
startIndex: start,
|
|
||||||
hd: hd,
|
|
||||||
isHidden: isHidden);
|
|
||||||
addrs.addAll(batch);
|
addrs.addAll(batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,21 +227,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
|
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
|
||||||
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
|
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
|
||||||
final newAddresses = await _createNewAddresses(
|
final newAddresses = await _createNewAddresses(addressesCount,
|
||||||
addressesCount,
|
startIndex: countOfReceiveAddresses, hd: mainHd, isHidden: false);
|
||||||
startIndex: countOfReceiveAddresses,
|
|
||||||
hd: mainHd,
|
|
||||||
isHidden: false);
|
|
||||||
addresses.addAll(newAddresses);
|
addresses.addAll(newAddresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
|
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
|
||||||
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
|
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
|
||||||
final newAddresses = await _createNewAddresses(
|
final newAddresses = await _createNewAddresses(addressesCount,
|
||||||
addressesCount,
|
startIndex: countOfHiddenAddresses, hd: sideHd, isHidden: true);
|
||||||
startIndex: countOfHiddenAddresses,
|
|
||||||
hd: sideHd,
|
|
||||||
isHidden: true);
|
|
||||||
addresses.addAll(newAddresses);
|
addresses.addAll(newAddresses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,10 +245,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
final list = <BitcoinAddressRecord>[];
|
final list = <BitcoinAddressRecord>[];
|
||||||
|
|
||||||
for (var i = startIndex; i < count + startIndex; i++) {
|
for (var i = startIndex; i < count + startIndex; i++) {
|
||||||
final address = BitcoinAddressRecord(
|
final address =
|
||||||
getAddress(index: i, hd: hd),
|
BitcoinAddressRecord(getAddress(index: i, hd: hd), index: i, isHidden: isHidden);
|
||||||
index: i,
|
|
||||||
isHidden: isHidden);
|
|
||||||
list.add(address);
|
list.add(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,4 +265,4 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
final transactionHistory = await electrumClient.getHistory(sh);
|
final transactionHistory = await electrumClient.getHistory(sh);
|
||||||
return transactionHistory.isNotEmpty;
|
return transactionHistory.isNotEmpty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/encryption_file_utils.dart';
|
import 'package:cw_bitcoin/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
|
||||||
class ElectrumWallletSnapshot {
|
class ElectrumWallletSnapshot {
|
||||||
ElectrumWallletSnapshot({
|
ElectrumWallletSnapshot({
|
||||||
|
@ -13,6 +14,7 @@ class ElectrumWallletSnapshot {
|
||||||
required this.mnemonic,
|
required this.mnemonic,
|
||||||
required this.addresses,
|
required this.addresses,
|
||||||
required this.balance,
|
required this.balance,
|
||||||
|
required this.networkType,
|
||||||
required this.regularAddressIndex,
|
required this.regularAddressIndex,
|
||||||
required this.changeAddressIndex});
|
required this.changeAddressIndex});
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ class ElectrumWallletSnapshot {
|
||||||
String mnemonic;
|
String mnemonic;
|
||||||
List<BitcoinAddressRecord> addresses;
|
List<BitcoinAddressRecord> addresses;
|
||||||
ElectrumBalance balance;
|
ElectrumBalance balance;
|
||||||
|
bitcoin.NetworkType networkType;
|
||||||
int regularAddressIndex;
|
int regularAddressIndex;
|
||||||
int changeAddressIndex;
|
int changeAddressIndex;
|
||||||
|
|
||||||
|
@ -38,6 +41,7 @@ class ElectrumWallletSnapshot {
|
||||||
.toList();
|
.toList();
|
||||||
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
|
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
|
||||||
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||||
|
final networkType = bitcoin.testnet;
|
||||||
var regularAddressIndex = 0;
|
var regularAddressIndex = 0;
|
||||||
var changeAddressIndex = 0;
|
var changeAddressIndex = 0;
|
||||||
|
|
||||||
|
@ -53,6 +57,7 @@ class ElectrumWallletSnapshot {
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
addresses: addresses,
|
addresses: addresses,
|
||||||
balance: balance,
|
balance: balance,
|
||||||
|
networkType: networkType,
|
||||||
regularAddressIndex: regularAddressIndex,
|
regularAddressIndex: regularAddressIndex,
|
||||||
changeAddressIndex: changeAddressIndex);
|
changeAddressIndex: changeAddressIndex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
WalletType getType() => WalletType.litecoin;
|
WalletType getType() => WalletType.litecoin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
final wallet = await LitecoinWalletBase.create(
|
final wallet = await LitecoinWalletBase.create(
|
||||||
mnemonic: await generateMnemonic(),
|
mnemonic: await generateMnemonic(),
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
|
|
|
@ -66,15 +66,27 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "1.0.6"
|
||||||
|
bitcoin_base:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "/home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_base"
|
||||||
|
relative: false
|
||||||
|
source: path
|
||||||
|
version: "1.1.0"
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "/home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_flutter"
|
||||||
ref: cake-update-v3
|
relative: false
|
||||||
resolved-ref: df9204144011ed9419eff7d9ef3143102a40252d
|
source: path
|
||||||
url: "https://github.com/cake-tech/bitcoin_flutter.git"
|
|
||||||
source: git
|
|
||||||
version: "2.0.2"
|
version: "2.0.2"
|
||||||
|
blockchain_utils:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "/home/rafael/Storage/Repositories/btc-silent-payments/blockchain_utils"
|
||||||
|
relative: false
|
||||||
|
source: path
|
||||||
|
version: "0.4.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -196,6 +208,13 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.0"
|
version: "4.4.0"
|
||||||
|
coinlib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "/home/rafael/Storage/Repositories/btc-silent-payments/coinlib/coinlib"
|
||||||
|
relative: false
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -243,6 +262,22 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
dart_base_x:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_base_x
|
||||||
|
sha256: c8af4f6a6518daab4aa85bb27ee148221644e80446bb44117052b6f4674cdb23
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
dart_bech32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_bech32
|
||||||
|
sha256: "0e1dc1ff39c9669c9ffeafd5d675104918f7b50799692491badfea7e1fb40888"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -251,6 +286,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.4"
|
version: "2.2.4"
|
||||||
|
elliptic:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: elliptic
|
||||||
|
sha256: "98e2fa89a714c649174553c823db2612dc9581814477fe1264a499d448237b6b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.10"
|
||||||
encrypt:
|
encrypt:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -271,10 +314,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.1.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -553,10 +596,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pointycastle
|
name: pointycastle
|
||||||
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
|
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.6.2"
|
version: "3.7.3"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -710,10 +753,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.2"
|
||||||
unorm_dart:
|
unorm_dart:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -730,6 +773,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
wasm_interop:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: wasm_interop
|
||||||
|
sha256: b1b378f07a4cf0103c25faf34d9a64d2c3312135b9efb47e0ec116ec3b14e48f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -20,9 +20,9 @@ dependencies:
|
||||||
cw_core:
|
cw_core:
|
||||||
path: ../cw_core
|
path: ../cw_core
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
git:
|
path: /home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_flutter
|
||||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
bitcoin_base:
|
||||||
ref: cake-update-v3
|
path: /home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_base
|
||||||
rxdart: ^0.27.5
|
rxdart: ^0.27.5
|
||||||
unorm_dart: ^0.2.0
|
unorm_dart: ^0.2.0
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.0.5
|
||||||
|
|
|
@ -6,7 +6,7 @@ abstract class WalletService<N extends WalletCredentials,
|
||||||
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
||||||
WalletType getType();
|
WalletType getType();
|
||||||
|
|
||||||
Future<WalletBase> create(N credentials);
|
Future<WalletBase> create(N credentials, {bool? isTestnet});
|
||||||
|
|
||||||
Future<WalletBase> restoreFromSeed(RFS credentials);
|
Future<WalletBase> restoreFromSeed(RFS credentials);
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
|
||||||
final bool isDirect;
|
final bool isDirect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
|
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
final mnemonic = bip39.generateMnemonic();
|
final mnemonic = bip39.generateMnemonic();
|
||||||
final wallet = EthereumWallet(
|
final wallet = EthereumWallet(
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
|
|
|
@ -68,7 +68,7 @@ class HavenWalletService extends WalletService<
|
||||||
WalletType getType() => WalletType.haven;
|
WalletType getType() => WalletType.haven;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<HavenWallet> create(HavenNewWalletCredentials credentials) async {
|
Future<HavenWallet> create(HavenNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await haven_wallet_manager.createWallet(
|
await haven_wallet_manager.createWallet(
|
||||||
|
|
|
@ -65,7 +65,7 @@ class MoneroWalletService extends WalletService<
|
||||||
WalletType getType() => WalletType.monero;
|
WalletType getType() => WalletType.monero;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
|
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
await monero_wallet_manager.createWallet(
|
await monero_wallet_manager.createWallet(
|
||||||
|
|
|
@ -105,6 +105,16 @@ class CWBitcoin extends Bitcoin {
|
||||||
return bitcoinWallet.walletAddresses.address;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String formatterBitcoinAmountToString({required int amount})
|
String formatterBitcoinAmountToString({required int amount})
|
||||||
=> bitcoinAmountToString(amount: amount);
|
=> bitcoinAmountToString(amount: amount);
|
||||||
|
|
|
@ -25,7 +25,10 @@ class AddressValidator extends TextValidator {
|
||||||
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
|
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}\$';
|
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
|
||||||
case CryptoCurrency.btc:
|
case CryptoCurrency.btc:
|
||||||
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
|
final p2sh = '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$';
|
||||||
|
final testnet = '^tb1[0-9a-zA-Z]{59}\$';
|
||||||
|
final silentpayments = '^tsp1[0-9a-zA-Z]{113}\$';
|
||||||
|
return '^bc1[0-9a-zA-Z]{59}\$|$p2sh|$testnet|$silentpayments';
|
||||||
case CryptoCurrency.nano:
|
case CryptoCurrency.nano:
|
||||||
return '[0-9a-zA-Z_]';
|
return '[0-9a-zA-Z_]';
|
||||||
case CryptoCurrency.usdc:
|
case CryptoCurrency.usdc:
|
||||||
|
|
|
@ -53,7 +53,7 @@ class WalletCreationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<WalletBase> create(WalletCredentials credentials) async {
|
Future<WalletBase> create(WalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
checkIfExists(credentials.name);
|
checkIfExists(credentials.name);
|
||||||
|
|
||||||
if (credentials.password == null) {
|
if (credentials.password == null) {
|
||||||
|
@ -62,7 +62,7 @@ class WalletCreationService {
|
||||||
password: credentials.password!, walletName: credentials.name);
|
password: credentials.password!, walletName: credentials.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
final wallet = await _service!.create(credentials);
|
final wallet = await _service!.create(credentials, isTestnet: isTestnet);
|
||||||
|
|
||||||
if (wallet.type == WalletType.monero) {
|
if (wallet.type == WalletType.monero) {
|
||||||
await sharedPreferences
|
await sharedPreferences
|
||||||
|
|
|
@ -1054,7 +1054,7 @@ Future<void> setup({
|
||||||
IoniaPaymentStatusPage(
|
IoniaPaymentStatusPage(
|
||||||
getIt.get<IoniaPaymentStatusViewModel>(param1: paymentInfo, param2: committedInfo)));
|
getIt.get<IoniaPaymentStatusViewModel>(param1: paymentInfo, param2: committedInfo)));
|
||||||
|
|
||||||
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
|
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, void Function(), void>(
|
||||||
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
|
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>((args, _) {
|
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>((args, _) {
|
||||||
|
|
123
lib/nano/nano.dart
Normal file
123
lib/nano/nano.dart
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import 'package:cw_core/cake_hive.dart';
|
||||||
|
import 'package:cw_core/nano_account.dart';
|
||||||
|
import 'package:cw_core/account.dart';
|
||||||
|
import 'package:cw_core/node.dart';
|
||||||
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
import 'package:cw_core/output_info.dart';
|
||||||
|
import 'package:cw_core/nano_account_info_response.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:cake_wallet/view_model/send/output.dart';
|
||||||
|
|
||||||
|
import 'package:cw_nano/nano_client.dart';
|
||||||
|
import 'package:cw_nano/nano_mnemonic.dart';
|
||||||
|
import 'package:cw_nano/nano_wallet.dart';
|
||||||
|
import 'package:cw_nano/nano_wallet_service.dart';
|
||||||
|
import 'package:cw_nano/nano_transaction_info.dart';
|
||||||
|
import 'package:cw_nano/nano_transaction_credentials.dart';
|
||||||
|
import 'package:cw_nano/nano_wallet_creation_credentials.dart';
|
||||||
|
// needed for nano_util:
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:convert/convert.dart';
|
||||||
|
import "package:ed25519_hd_key/ed25519_hd_key.dart";
|
||||||
|
import 'package:libcrypto/libcrypto.dart';
|
||||||
|
import 'package:nanodart/nanodart.dart' as ND;
|
||||||
|
import 'package:decimal/decimal.dart';
|
||||||
|
|
||||||
|
part 'cw_nano.dart';
|
||||||
|
|
||||||
|
Nano? nano = CWNano();
|
||||||
|
NanoUtil? nanoUtil = CWNanoUtil();
|
||||||
|
|
||||||
|
abstract class Nano {
|
||||||
|
NanoAccountList getAccountList(Object wallet);
|
||||||
|
|
||||||
|
Account getCurrentAccount(Object wallet);
|
||||||
|
|
||||||
|
void setCurrentAccount(Object wallet, int id, String label, String? balance);
|
||||||
|
|
||||||
|
WalletService createNanoWalletService(Box<WalletInfo> walletInfoSource, bool isDirect);
|
||||||
|
|
||||||
|
WalletCredentials createNanoNewWalletCredentials({
|
||||||
|
required String name,
|
||||||
|
String? password,
|
||||||
|
});
|
||||||
|
|
||||||
|
WalletCredentials createNanoRestoreWalletFromSeedCredentials({
|
||||||
|
required String name,
|
||||||
|
required String password,
|
||||||
|
required String mnemonic,
|
||||||
|
DerivationType? derivationType,
|
||||||
|
});
|
||||||
|
|
||||||
|
WalletCredentials createNanoRestoreWalletFromKeysCredentials({
|
||||||
|
required String name,
|
||||||
|
required String password,
|
||||||
|
required String seedKey,
|
||||||
|
DerivationType? derivationType,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<String> getNanoWordList(String language);
|
||||||
|
Map<String, String> getKeys(Object wallet);
|
||||||
|
Object createNanoTransactionCredentials(List<Output> outputs);
|
||||||
|
Future<void> changeRep(Object wallet, String address);
|
||||||
|
Future<void> updateTransactions(Object wallet);
|
||||||
|
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class NanoAccountList {
|
||||||
|
ObservableList<NanoAccount> get accounts;
|
||||||
|
void update(Object wallet);
|
||||||
|
void refresh(Object wallet);
|
||||||
|
Future<List<NanoAccount>> getAll(Object wallet);
|
||||||
|
Future<void> addAccount(Object wallet, {required String label});
|
||||||
|
Future<void> setLabelAccount(Object wallet, {required int accountIndex, required String label});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class NanoUtil {
|
||||||
|
String seedToPrivate(String seed, int index);
|
||||||
|
String seedToAddress(String seed, int index);
|
||||||
|
String seedToMnemonic(String seed);
|
||||||
|
Future<String> mnemonicToSeed(String mnemonic);
|
||||||
|
String privateKeyToPublic(String privateKey);
|
||||||
|
String addressToPublicKey(String publicAddress);
|
||||||
|
String privateKeyToAddress(String privateKey);
|
||||||
|
String publicKeyToAddress(String publicKey);
|
||||||
|
bool isValidSeed(String seed);
|
||||||
|
Future<String> hdMnemonicListToSeed(List<String> words);
|
||||||
|
Future<String> hdSeedToPrivate(String seed, int index);
|
||||||
|
Future<String> hdSeedToAddress(String seed, int index);
|
||||||
|
Future<String> uniSeedToAddress(String seed, int index, String type);
|
||||||
|
Future<String> uniSeedToPrivate(String seed, int index, String type);
|
||||||
|
bool isValidBip39Seed(String seed);
|
||||||
|
static const int maxDecimalDigits = 6; // Max digits after decimal
|
||||||
|
BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
|
||||||
|
BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
|
||||||
|
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
|
||||||
|
BigInt rawPerXMR = BigInt.parse("1000000000000");
|
||||||
|
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
|
||||||
|
Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur);
|
||||||
|
String truncateDecimal(Decimal input, {int digits = maxDecimalDigits});
|
||||||
|
String getRawAsUsableString(String? raw, BigInt rawPerCur);
|
||||||
|
String getRawAccuracy(String? raw, BigInt rawPerCur);
|
||||||
|
String getAmountAsRaw(String amount, BigInt rawPerCur);
|
||||||
|
|
||||||
|
// derivationInfo:
|
||||||
|
Future<AccountInfoResponse?> getInfoFromSeedOrMnemonic(
|
||||||
|
DerivationType derivationType, {
|
||||||
|
String? seedKey,
|
||||||
|
String? mnemonic,
|
||||||
|
required Node node,
|
||||||
|
});
|
||||||
|
Future<List<DerivationType>> compareDerivationMethods({
|
||||||
|
String? mnemonic,
|
||||||
|
String? privateKey,
|
||||||
|
required Node node,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -571,11 +571,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
param2: url));
|
param2: url));
|
||||||
|
|
||||||
case Routes.advancedPrivacySettings:
|
case Routes.advancedPrivacySettings:
|
||||||
final type = settings.arguments as WalletType;
|
final args = settings.arguments as List;
|
||||||
|
final type = args.first as WalletType;
|
||||||
|
final func = args[1] as void Function();
|
||||||
|
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) => AdvancedPrivacySettingsPage(
|
builder: (_) => AdvancedPrivacySettingsPage(
|
||||||
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
|
getIt.get<AdvancedPrivacySettingsViewModel>(param1: func),
|
||||||
getIt.get<NodeCreateOrEditViewModel>(param1: type),
|
getIt.get<NodeCreateOrEditViewModel>(param1: type),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -177,8 +177,7 @@ class AddressPage extends BasePage {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled
|
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled
|
||||||
? await showPopUp<void>(
|
? await showPopUp<void>(
|
||||||
context: context,
|
context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
|
||||||
builder: (_) => getIt.get<MoneroAccountListPage>())
|
|
||||||
: Navigator.of(context).pushNamed(Routes.receive),
|
: Navigator.of(context).pushNamed(Routes.receive),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
|
@ -198,26 +197,29 @@ class AddressPage extends BasePage {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
String label = addressListViewModel.hasAccounts
|
String label = addressListViewModel.hasSilentAddresses
|
||||||
? S.of(context).accounts_subaddresses
|
? S.of(context).address_and_silent_addresses
|
||||||
: S.of(context).addresses;
|
: addressListViewModel.hasAccounts
|
||||||
|
? S.of(context).accounts_subaddresses
|
||||||
|
: S.of(context).addresses;
|
||||||
|
|
||||||
if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) {
|
if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) {
|
||||||
label = addressListViewModel.hasAccounts
|
label = addressListViewModel.hasAccounts
|
||||||
? S.of(context).accounts
|
? S.of(context).accounts
|
||||||
: S.of(context).account;
|
: S.of(context).account;
|
||||||
}
|
}
|
||||||
return Text(
|
return Text(
|
||||||
label,
|
label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<SyncIndicatorTheme>()!
|
.extension<SyncIndicatorTheme>()!
|
||||||
.textColor),
|
.textColor),
|
||||||
);
|
);
|
||||||
},),
|
},
|
||||||
|
),
|
||||||
Icon(
|
Icon(
|
||||||
Icons.arrow_forward_ios,
|
Icons.arrow_forward_ios,
|
||||||
size: 14,
|
size: 14,
|
||||||
|
@ -227,7 +229,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,
|
return Text(S.of(context).electrum_address_disclaimer,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
|
@ -75,6 +75,18 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
if (widget.nodeViewModel.hasTestnetSupport)
|
||||||
|
Observer(builder: (_) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SettingsSwitcherCell(
|
||||||
|
title: S.current.use_testnet,
|
||||||
|
value: widget.privacySettingsViewModel.useTestnet,
|
||||||
|
onValueChange: (_, __) => widget.privacySettingsViewModel.toggleUseTestnet(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
Observer(builder: (_) {
|
Observer(builder: (_) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
@ -297,8 +297,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context)
|
Navigator.of(context).pushNamed(Routes.advancedPrivacySettings,
|
||||||
.pushNamed(Routes.advancedPrivacySettings, arguments: _walletNewVM.type);
|
arguments: [_walletNewVM.type, _walletNewVM.toggleUseTestnet]);
|
||||||
},
|
},
|
||||||
child: Text(S.of(context).advanced_privacy_settings),
|
child: Text(S.of(context).advanced_privacy_settings),
|
||||||
),
|
),
|
||||||
|
|
|
@ -66,8 +66,7 @@ class ReceivePage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget Function(BuildContext, Widget) get rootWrapper =>
|
Widget Function(BuildContext, Widget) get rootWrapper =>
|
||||||
(BuildContext context, Widget scaffold) =>
|
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
|
||||||
GradientBackground(scaffold: scaffold);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget trailing(BuildContext context) {
|
Widget trailing(BuildContext context) {
|
||||||
|
@ -98,7 +97,8 @@ class ReceivePage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
return (addressListViewModel.type == WalletType.monero ||
|
return (addressListViewModel.type == WalletType.bitcoin ||
|
||||||
|
addressListViewModel.type == WalletType.monero ||
|
||||||
addressListViewModel.type == WalletType.haven)
|
addressListViewModel.type == WalletType.haven)
|
||||||
? KeyboardActions(
|
? KeyboardActions(
|
||||||
config: KeyboardActionsConfig(
|
config: KeyboardActionsConfig(
|
||||||
|
@ -144,7 +144,8 @@ class ReceivePage extends BasePage {
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_forward_ios,
|
Icons.arrow_forward_ios,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
color:
|
||||||
|
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,11 +153,12 @@ class ReceivePage extends BasePage {
|
||||||
cell = HeaderTile(
|
cell = HeaderTile(
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
Navigator.of(context).pushNamed(Routes.newSubaddress),
|
||||||
title: S.of(context).addresses,
|
title: S.of(context).silent_addresses,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
color:
|
||||||
|
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,11 +167,19 @@ class ReceivePage extends BasePage {
|
||||||
final isCurrent =
|
final isCurrent =
|
||||||
item.address == addressListViewModel.address.address;
|
item.address == addressListViewModel.address.address;
|
||||||
final backgroundColor = isCurrent
|
final backgroundColor = isCurrent
|
||||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
|
? Theme.of(context)
|
||||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
|
.extension<ReceivePageTheme>()!
|
||||||
|
.currentTileBackgroundColor
|
||||||
|
: Theme.of(context)
|
||||||
|
.extension<ReceivePageTheme>()!
|
||||||
|
.tilesBackgroundColor;
|
||||||
final textColor = isCurrent
|
final textColor = isCurrent
|
||||||
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
|
? Theme.of(context)
|
||||||
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
|
.extension<ReceivePageTheme>()!
|
||||||
|
.currentTileTextColor
|
||||||
|
: Theme.of(context)
|
||||||
|
.extension<ReceivePageTheme>()!
|
||||||
|
.tilesTextColor;
|
||||||
|
|
||||||
return AddressCell.fromItem(item,
|
return AddressCell.fromItem(item,
|
||||||
isCurrent: isCurrent,
|
isCurrent: isCurrent,
|
||||||
|
@ -190,6 +200,15 @@ class ReceivePage extends BasePage {
|
||||||
child: cell,
|
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)),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:cake_wallet/core/seed_validator.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class Annotation extends Comparable<Annotation> {
|
class Annotation implements Comparable<Annotation> {
|
||||||
Annotation({required this.range, required this.style});
|
Annotation({required this.range, required this.style});
|
||||||
|
|
||||||
final TextRange range;
|
final TextRange range;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'advanced_privacy_settings_view_model.g.dart';
|
part 'advanced_privacy_settings_view_model.g.dart';
|
||||||
|
@ -10,7 +9,8 @@ class AdvancedPrivacySettingsViewModel = AdvancedPrivacySettingsViewModelBase
|
||||||
with _$AdvancedPrivacySettingsViewModel;
|
with _$AdvancedPrivacySettingsViewModel;
|
||||||
|
|
||||||
abstract class AdvancedPrivacySettingsViewModelBase with Store {
|
abstract class AdvancedPrivacySettingsViewModelBase with Store {
|
||||||
AdvancedPrivacySettingsViewModelBase(this.type, this._settingsStore) : _addCustomNode = false;
|
AdvancedPrivacySettingsViewModelBase(this.changeUseTestnet, this._settingsStore)
|
||||||
|
: _addCustomNode = false;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
||||||
|
@ -21,13 +21,20 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool _addCustomNode = false;
|
bool _addCustomNode = false;
|
||||||
|
|
||||||
final WalletType type;
|
@observable
|
||||||
|
bool _useTestnet = false;
|
||||||
|
|
||||||
|
// TODO: electrum's node as default for testnet
|
||||||
|
final void Function() changeUseTestnet;
|
||||||
|
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get addCustomNode => _addCustomNode;
|
bool get addCustomNode => _addCustomNode;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get useTestnet => _useTestnet;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode;
|
void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode;
|
||||||
|
|
||||||
|
@ -36,4 +43,10 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void toggleAddCustomNode() => _addCustomNode = !_addCustomNode;
|
void toggleAddCustomNode() => _addCustomNode = !_addCustomNode;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void toggleUseTestnet() {
|
||||||
|
_useTestnet = !_useTestnet;
|
||||||
|
changeUseTestnet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
||||||
bool get hasAuthCredentials =>
|
bool get hasAuthCredentials =>
|
||||||
_walletType == WalletType.monero || _walletType == WalletType.haven;
|
_walletType == WalletType.monero || _walletType == WalletType.haven;
|
||||||
|
|
||||||
|
bool get hasTestnetSupport => _walletType == WalletType.bitcoin;
|
||||||
|
|
||||||
String get uri {
|
String get uri {
|
||||||
var uri = address;
|
var uri = address;
|
||||||
|
|
||||||
|
|
|
@ -115,8 +115,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
}) : _baseItems = <ListItem>[],
|
}) : _baseItems = <ListItem>[],
|
||||||
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
|
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
|
||||||
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
|
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
|
||||||
hasAccounts =
|
hasAccounts = appStore.wallet!.type == WalletType.bitcoin ||
|
||||||
appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.haven,
|
appStore.wallet!.type == WalletType.monero ||
|
||||||
|
appStore.wallet!.type == WalletType.haven,
|
||||||
amount = '',
|
amount = '',
|
||||||
super(appStore: appStore) {
|
super(appStore: appStore) {
|
||||||
_init();
|
_init();
|
||||||
|
@ -127,7 +128,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
_init();
|
_init();
|
||||||
|
|
||||||
selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
|
selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
|
||||||
hasAccounts = wallet.type == WalletType.monero || wallet.type == WalletType.haven;
|
hasAccounts = wallet.type == WalletType.bitcoin ||
|
||||||
|
wallet.type == WalletType.monero ||
|
||||||
|
wallet.type == WalletType.haven;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const String _cryptoNumberPattern = '0.00000000';
|
static const String _cryptoNumberPattern = '0.00000000';
|
||||||
|
@ -217,9 +220,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wallet.type == WalletType.bitcoin) {
|
if (wallet.type == WalletType.bitcoin) {
|
||||||
final primaryAddress = bitcoin!.getAddress(wallet);
|
final receiveAddress = bitcoin!.getReceiveAddress(wallet);
|
||||||
final bitcoinAddresses = bitcoin!.getAddresses(wallet).map((addr) {
|
final silentAddress = bitcoin!.getSilentAddress(wallet).toString();
|
||||||
final isPrimary = addr == primaryAddress;
|
final bitcoinAddresses = [receiveAddress, silentAddress].map((addr) {
|
||||||
|
final isPrimary = addr == receiveAddress;
|
||||||
|
|
||||||
return WalletAddressListItem(isPrimary: isPrimary, name: null, address: addr);
|
return WalletAddressListItem(isPrimary: isPrimary, name: null, address: addr);
|
||||||
});
|
});
|
||||||
|
@ -252,7 +256,13 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get hasAddressList => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
|
bool get hasSilentAddresses => wallet.type == WalletType.bitcoin;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get hasAddressList =>
|
||||||
|
wallet.type == WalletType.bitcoin ||
|
||||||
|
wallet.type == WalletType.monero ||
|
||||||
|
wallet.type == WalletType.haven;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get showElectrumAddressDisclaimer =>
|
bool get showElectrumAddressDisclaimer =>
|
||||||
|
|
|
@ -51,7 +51,7 @@ abstract class WalletCreationVMBase with Store {
|
||||||
bool typeExists(WalletType type)
|
bool typeExists(WalletType type)
|
||||||
=> walletCreationService.typeExists(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;
|
final type = restoreWallet?.type ?? this.type;
|
||||||
try {
|
try {
|
||||||
state = IsExecutingState();
|
state = IsExecutingState();
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
|
@ -25,6 +23,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||||
: selectedMnemonicLanguage = '',
|
: selectedMnemonicLanguage = '',
|
||||||
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: false);
|
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: false);
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool _useTestnet = false;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get useTestnet => _useTestnet;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
String selectedMnemonicLanguage;
|
String selectedMnemonicLanguage;
|
||||||
|
|
||||||
|
@ -46,13 +50,16 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
return ethereum!.createEthereumNewWalletCredentials(name: name, password: walletPassword);
|
return ethereum!.createEthereumNewWalletCredentials(name: name, password: walletPassword);
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected type: ${type.toString()}');;
|
throw Exception('Unexpected type: ${type.toString()}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<WalletBase> process(WalletCredentials credentials) async {
|
Future<WalletBase> process(WalletCredentials credentials) async {
|
||||||
walletCreationService.changeWalletType(type: type);
|
walletCreationService.changeWalletType(type: type);
|
||||||
return walletCreationService.create(credentials);
|
return walletCreationService.create(credentials, isTestnet: useTestnet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void toggleUseTestnet() => _useTestnet = !_useTestnet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
#include <cw_monero/cw_monero_plugin.h>
|
#include <cw_monero/cw_monero_plugin.h>
|
||||||
#include <devicelocale/devicelocale_plugin.h>
|
#include <devicelocale/devicelocale_plugin.h>
|
||||||
#include <platform_device_id_linux/platform_device_id_linux_plugin.h>
|
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
@ -18,9 +17,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) devicelocale_registrar =
|
g_autoptr(FlPluginRegistrar) devicelocale_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DevicelocalePlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "DevicelocalePlugin");
|
||||||
devicelocale_plugin_register_with_registrar(devicelocale_registrar);
|
devicelocale_plugin_register_with_registrar(devicelocale_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) platform_device_id_linux_registrar =
|
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PlatformDeviceIdLinuxPlugin");
|
|
||||||
platform_device_id_linux_plugin_register_with_registrar(platform_device_id_linux_registrar);
|
|
||||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
cw_monero
|
cw_monero
|
||||||
devicelocale
|
devicelocale
|
||||||
platform_device_id_linux
|
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,6 @@ import devicelocale
|
||||||
import in_app_review
|
import in_app_review
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import platform_device_id
|
|
||||||
import platform_device_id_macos
|
|
||||||
import share_plus_macos
|
import share_plus_macos
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
@ -27,8 +25,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
||||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
PlatformDeviceIdMacosPlugin.register(with: registry.registrar(forPlugin: "PlatformDeviceIdMacosPlugin"))
|
|
||||||
PlatformDeviceIdMacosPlugin.register(with: registry.registrar(forPlugin: "PlatformDeviceIdMacosPlugin"))
|
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
|
|
@ -79,9 +79,7 @@ dependencies:
|
||||||
url_launcher_android: 6.0.24
|
url_launcher_android: 6.0.24
|
||||||
sensitive_clipboard: ^1.0.0
|
sensitive_clipboard: ^1.0.0
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
git:
|
path: /home/rafael/Storage/Repositories/btc-silent-payments/bitcoin_flutter
|
||||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
|
||||||
ref: cake-update-v3
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -697,6 +697,8 @@
|
||||||
"buy_provider_unavailable": "مزود حاليا غير متوفر.",
|
"buy_provider_unavailable": "مزود حاليا غير متوفر.",
|
||||||
|
|
||||||
"do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.",
|
"do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.",
|
||||||
"totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ"
|
"totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ",
|
||||||
|
"use_testnet": "استخدم testnet",
|
||||||
|
"address_and_silent_addresses": "العنوان والعناوين الصامتة",
|
||||||
|
"silent_addresses": "عناوين صامتة"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -692,5 +692,8 @@
|
||||||
"ask_each_time": "Питайте всеки път",
|
"ask_each_time": "Питайте всеки път",
|
||||||
"buy_provider_unavailable": "Понастоящем доставчик не е наличен.",
|
"buy_provider_unavailable": "Понастоящем доставчик не е наличен.",
|
||||||
"do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.",
|
"do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.",
|
||||||
"totp_auth_url": "TOTP AUTH URL"
|
"totp_auth_url": "TOTP AUTH URL",
|
||||||
|
"use_testnet": "Използвайте TestNet",
|
||||||
|
"address_and_silent_addresses": "Адрес и мълчаливи адреси",
|
||||||
|
"silent_addresses": "Безшумни адреси"
|
||||||
}
|
}
|
||||||
|
|
|
@ -692,5 +692,8 @@
|
||||||
"ask_each_time": "Zeptejte se pokaždé",
|
"ask_each_time": "Zeptejte se pokaždé",
|
||||||
"buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.",
|
"buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.",
|
||||||
"do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.",
|
"do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.",
|
||||||
"totp_auth_url": "URL AUTH TOTP"
|
"totp_auth_url": "URL AUTH TOTP",
|
||||||
|
"use_testnet": "Použijte testNet",
|
||||||
|
"address_and_silent_addresses": "Adresa a tiché adresy",
|
||||||
|
"silent_addresses": "Tiché adresy"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Jedes Mal fragen",
|
"ask_each_time": "Jedes Mal fragen",
|
||||||
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
|
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
|
||||||
"do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.",
|
"do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.",
|
||||||
"totp_auth_url": "TOTP-Auth-URL"
|
"totp_auth_url": "TOTP-Auth-URL",
|
||||||
|
"use_testnet": "TESTNET verwenden",
|
||||||
|
"address_and_silent_addresses": "Adresse und stille Adressen",
|
||||||
|
"silent_addresses": "Stille Adressen"
|
||||||
}
|
}
|
||||||
|
|
|
@ -701,5 +701,8 @@
|
||||||
"robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.",
|
"robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.",
|
||||||
"buy_provider_unavailable": "Provider currently unavailable.",
|
"buy_provider_unavailable": "Provider currently unavailable.",
|
||||||
"do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.",
|
"do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.",
|
||||||
"totp_auth_url": "TOTP AUTH URL"
|
"totp_auth_url": "TOTP AUTH URL",
|
||||||
|
"use_testnet": "Use testnet",
|
||||||
|
"address_and_silent_addresses": "Address and Silent Addresses",
|
||||||
|
"silent_addresses": "Silent Addresses"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Pregunta cada vez",
|
"ask_each_time": "Pregunta cada vez",
|
||||||
"buy_provider_unavailable": "Proveedor actualmente no disponible.",
|
"buy_provider_unavailable": "Proveedor actualmente no disponible.",
|
||||||
"do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.",
|
"do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.",
|
||||||
"totp_auth_url": "URL de autenticación TOTP"
|
"totp_auth_url": "URL de autenticación TOTP",
|
||||||
|
"use_testnet": "Use TestNet",
|
||||||
|
"address_and_silent_addresses": "Dirección y direcciones silenciosas",
|
||||||
|
"silent_addresses": "Direcciones silenciosas"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Demandez à chaque fois",
|
"ask_each_time": "Demandez à chaque fois",
|
||||||
"buy_provider_unavailable": "Fournisseur actuellement indisponible.",
|
"buy_provider_unavailable": "Fournisseur actuellement indisponible.",
|
||||||
"do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.",
|
"do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.",
|
||||||
"totp_auth_url": "URL D'AUTORISATION TOTP"
|
"totp_auth_url": "URL D'AUTORISATION TOTP",
|
||||||
|
"use_testnet": "Utiliser TestNet",
|
||||||
|
"address_and_silent_addresses": "Adresse et adresses silencieuses",
|
||||||
|
"silent_addresses": "Adresses silencieuses"
|
||||||
}
|
}
|
||||||
|
|
|
@ -678,5 +678,8 @@
|
||||||
"ask_each_time": "Tambaya kowane lokaci",
|
"ask_each_time": "Tambaya kowane lokaci",
|
||||||
"buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.",
|
"buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.",
|
||||||
"do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.",
|
"do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.",
|
||||||
"totp_auth_url": "TOTP AUTH URL"
|
"totp_auth_url": "TOTP AUTH URL",
|
||||||
|
"use_testnet": "Amfani da gwaji",
|
||||||
|
"address_and_silent_addresses": "Adireshin da adreshin shiru",
|
||||||
|
"silent_addresses": "Adireshin Shiru"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "हर बार पूछें",
|
"ask_each_time": "हर बार पूछें",
|
||||||
"buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।",
|
"buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।",
|
||||||
"do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।",
|
"do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।",
|
||||||
"totp_auth_url": "TOTP प्रामाणिक यूआरएल"
|
"totp_auth_url": "TOTP प्रामाणिक यूआरएल",
|
||||||
|
"use_testnet": "टेस्टनेट का उपयोग करें",
|
||||||
|
"address_and_silent_addresses": "पता और मूक पते",
|
||||||
|
"silent_addresses": "मूक पते"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Pitajte svaki put",
|
"ask_each_time": "Pitajte svaki put",
|
||||||
"buy_provider_unavailable": "Davatelj trenutno nije dostupan.",
|
"buy_provider_unavailable": "Davatelj trenutno nije dostupan.",
|
||||||
"do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.",
|
"do_not_have_enough_gas_asset": "Nemate dovoljno ${currency} da izvršite transakciju s trenutačnim uvjetima blockchain mreže. Trebate više ${currency} da platite naknade za blockchain mrežu, čak i ako šaljete drugu imovinu.",
|
||||||
"totp_auth_url": "TOTP AUTH URL"
|
"totp_auth_url": "TOTP AUTH URL",
|
||||||
}
|
"use_testnet": "Koristite TestNet",
|
||||||
|
"address_and_silent_addresses": "Adresa i tihe adrese",
|
||||||
|
"silent_addresses": "Tihe adrese"
|
||||||
|
}
|
||||||
|
|
|
@ -688,5 +688,8 @@
|
||||||
"ask_each_time": "Tanyakan setiap kali",
|
"ask_each_time": "Tanyakan setiap kali",
|
||||||
"buy_provider_unavailable": "Penyedia saat ini tidak tersedia.",
|
"buy_provider_unavailable": "Penyedia saat ini tidak tersedia.",
|
||||||
"do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.",
|
"do_not_have_enough_gas_asset": "Anda tidak memiliki cukup ${currency} untuk melakukan transaksi dengan kondisi jaringan blockchain saat ini. Anda memerlukan lebih banyak ${currency} untuk membayar biaya jaringan blockchain, meskipun Anda mengirimkan aset yang berbeda.",
|
||||||
"totp_auth_url": "URL Otentikasi TOTP"
|
"totp_auth_url": "URL Otentikasi TOTP",
|
||||||
}
|
"use_testnet": "Gunakan TestNet",
|
||||||
|
"address_and_silent_addresses": "Alamat dan alamat diam",
|
||||||
|
"silent_addresses": "Alamat diam"
|
||||||
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Chiedi ogni volta",
|
"ask_each_time": "Chiedi ogni volta",
|
||||||
"buy_provider_unavailable": "Provider attualmente non disponibile.",
|
"buy_provider_unavailable": "Provider attualmente non disponibile.",
|
||||||
"do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.",
|
"do_not_have_enough_gas_asset": "Non hai abbastanza ${currency} per effettuare una transazione con le attuali condizioni della rete blockchain. Hai bisogno di più ${currency} per pagare le commissioni della rete blockchain, anche se stai inviando una risorsa diversa.",
|
||||||
"totp_auth_url": "URL DI AUT. TOTP"
|
"totp_auth_url": "URL DI AUT. TOTP",
|
||||||
|
"use_testnet": "Usa TestNet",
|
||||||
|
"address_and_silent_addresses": "Indirizzo e indirizzi silenziosi",
|
||||||
|
"silent_addresses": "Indirizzi silenziosi"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "毎回尋ねてください",
|
"ask_each_time": "毎回尋ねてください",
|
||||||
"buy_provider_unavailable": "現在、プロバイダーは利用できません。",
|
"buy_provider_unavailable": "現在、プロバイダーは利用できません。",
|
||||||
"do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。",
|
"do_not_have_enough_gas_asset": "現在のブロックチェーン ネットワークの状況では、トランザクションを行うのに十分な ${currency} がありません。別のアセットを送信する場合でも、ブロックチェーン ネットワーク料金を支払うにはさらに ${currency} が必要です。",
|
||||||
"totp_auth_url": "TOTP認証URL"
|
"totp_auth_url": "TOTP認証URL",
|
||||||
|
"use_testnet": "TestNetを使用します",
|
||||||
|
"address_and_silent_addresses": "住所とサイレントアドレス",
|
||||||
|
"silent_addresses": "サイレントアドレス"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "매번 물어보십시오",
|
"ask_each_time": "매번 물어보십시오",
|
||||||
"buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.",
|
"buy_provider_unavailable": "제공자는 현재 사용할 수 없습니다.",
|
||||||
"do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.",
|
"do_not_have_enough_gas_asset": "현재 블록체인 네트워크 조건으로 거래를 하기에는 ${currency}이(가) 충분하지 않습니다. 다른 자산을 보내더라도 블록체인 네트워크 수수료를 지불하려면 ${currency}가 더 필요합니다.",
|
||||||
"totp_auth_url": "TOTP 인증 URL"
|
"totp_auth_url": "TOTP 인증 URL",
|
||||||
|
"use_testnet": "TestNet을 사용하십시오",
|
||||||
|
"address_and_silent_addresses": "주소 및 조용한 주소",
|
||||||
|
"silent_addresses": "조용한 주소"
|
||||||
}
|
}
|
||||||
|
|
|
@ -698,5 +698,8 @@
|
||||||
"ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ",
|
"ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ",
|
||||||
"buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။",
|
"buy_provider_unavailable": "လက်ရှိတွင်လက်ရှိမရနိုင်ပါ။",
|
||||||
"do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။",
|
"do_not_have_enough_gas_asset": "လက်ရှိ blockchain ကွန်ရက်အခြေအနေများနှင့် အရောင်းအဝယ်ပြုလုပ်ရန် သင့်တွင် ${currency} လုံလောက်မှုမရှိပါ။ သင်သည် မတူညီသော ပိုင်ဆိုင်မှုတစ်ခုကို ပေးပို့နေသော်လည်း blockchain ကွန်ရက်အခကြေးငွေကို ပေးဆောင်ရန် သင်သည် နောက်ထပ် ${currency} လိုအပ်ပါသည်။",
|
||||||
"totp_auth_url": "TOTP AUTH URL"
|
"totp_auth_url": "TOTP AUTH URL",
|
||||||
|
"use_testnet": "testnet ကိုသုံးပါ",
|
||||||
|
"address_and_silent_addresses": "လိပ်စာနှင့်အသံတိတ်လိပ်စာများ",
|
||||||
|
"silent_addresses": "အသံတိတ်လိပ်စာများ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Vraag het elke keer",
|
"ask_each_time": "Vraag het elke keer",
|
||||||
"buy_provider_unavailable": "Provider momenteel niet beschikbaar.",
|
"buy_provider_unavailable": "Provider momenteel niet beschikbaar.",
|
||||||
"do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.",
|
"do_not_have_enough_gas_asset": "U heeft niet genoeg ${currency} om een transactie uit te voeren met de huidige blockchain-netwerkomstandigheden. U heeft meer ${currency} nodig om blockchain-netwerkkosten te betalen, zelfs als u een ander item verzendt.",
|
||||||
"totp_auth_url": "TOTP AUTH-URL"
|
"totp_auth_url": "TOTP AUTH-URL",
|
||||||
|
"use_testnet": "Gebruik testnet",
|
||||||
|
"address_and_silent_addresses": "Adres en stille adressen",
|
||||||
|
"silent_addresses": "Stille adressen"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Zapytaj za każdym razem",
|
"ask_each_time": "Zapytaj za każdym razem",
|
||||||
"buy_provider_unavailable": "Dostawca obecnie niedostępny.",
|
"buy_provider_unavailable": "Dostawca obecnie niedostępny.",
|
||||||
"do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.",
|
"do_not_have_enough_gas_asset": "Nie masz wystarczającej ilości ${currency}, aby dokonać transakcji przy bieżących warunkach sieci blockchain. Potrzebujesz więcej ${currency}, aby uiścić opłaty za sieć blockchain, nawet jeśli wysyłasz inny zasób.",
|
||||||
"totp_auth_url": "Adres URL TOTP AUTH"
|
"totp_auth_url": "Adres URL TOTP AUTH",
|
||||||
|
"use_testnet": "Użyj testne",
|
||||||
|
"address_and_silent_addresses": "Adres i ciche adresy",
|
||||||
|
"silent_addresses": "Ciche adresy"
|
||||||
}
|
}
|
||||||
|
|
|
@ -699,5 +699,8 @@
|
||||||
"ask_each_time": "Pergunte cada vez",
|
"ask_each_time": "Pergunte cada vez",
|
||||||
"buy_provider_unavailable": "Provedor atualmente indisponível.",
|
"buy_provider_unavailable": "Provedor atualmente indisponível.",
|
||||||
"do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.",
|
"do_not_have_enough_gas_asset": "Você não tem ${currency} suficiente para fazer uma transação com as condições atuais da rede blockchain. Você precisa de mais ${currency} para pagar as taxas da rede blockchain, mesmo se estiver enviando um ativo diferente.",
|
||||||
"totp_auth_url": "URL de autenticação TOTP"
|
"totp_auth_url": "URL de autenticação TOTP",
|
||||||
|
"use_testnet": "Use testNet",
|
||||||
|
"address_and_silent_addresses": "Endereço e endereços silenciosos",
|
||||||
|
"silent_addresses": "Endereços silenciosos"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Спросите каждый раз",
|
"ask_each_time": "Спросите каждый раз",
|
||||||
"buy_provider_unavailable": "Поставщик в настоящее время недоступен.",
|
"buy_provider_unavailable": "Поставщик в настоящее время недоступен.",
|
||||||
"do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.",
|
"do_not_have_enough_gas_asset": "У вас недостаточно ${currency} для совершения транзакции при текущих условиях сети блокчейн. Вам нужно больше ${currency} для оплаты комиссий за сеть блокчейна, даже если вы отправляете другой актив.",
|
||||||
"totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ"
|
"totp_auth_url": "URL-адрес TOTP-АВТОРИЗАЦИИ",
|
||||||
|
"use_testnet": "Используйте Testnet",
|
||||||
|
"address_and_silent_addresses": "Адрес и молчаливые адреса",
|
||||||
|
"silent_addresses": "Молчаливые адреса"
|
||||||
}
|
}
|
||||||
|
|
|
@ -698,5 +698,8 @@
|
||||||
"ask_each_time": "ถามทุกครั้ง",
|
"ask_each_time": "ถามทุกครั้ง",
|
||||||
"buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน",
|
"buy_provider_unavailable": "ผู้ให้บริการไม่สามารถใช้งานได้ในปัจจุบัน",
|
||||||
"do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม",
|
"do_not_have_enough_gas_asset": "คุณมี ${currency} ไม่เพียงพอที่จะทำธุรกรรมกับเงื่อนไขเครือข่ายบล็อคเชนในปัจจุบัน คุณต้องมี ${currency} เพิ่มขึ้นเพื่อชำระค่าธรรมเนียมเครือข่ายบล็อคเชน แม้ว่าคุณจะส่งสินทรัพย์อื่นก็ตาม",
|
||||||
"totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP"
|
"totp_auth_url": "URL การตรวจสอบสิทธิ์ TOTP",
|
||||||
|
"use_testnet": "ใช้ testnet",
|
||||||
|
"address_and_silent_addresses": "ที่อยู่และที่อยู่เงียบ",
|
||||||
|
"silent_addresses": "ที่อยู่เงียบ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -698,5 +698,8 @@
|
||||||
"ask_each_time": "Her seferinde sor",
|
"ask_each_time": "Her seferinde sor",
|
||||||
"buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.",
|
"buy_provider_unavailable": "Sağlayıcı şu anda kullanılamıyor.",
|
||||||
"do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.",
|
"do_not_have_enough_gas_asset": "Mevcut blockchain ağ koşullarıyla işlem yapmak için yeterli ${currency} paranız yok. Farklı bir varlık gönderiyor olsanız bile blockchain ağ ücretlerini ödemek için daha fazla ${currency} miktarına ihtiyacınız var.",
|
||||||
"totp_auth_url": "TOTP YETKİ URL'si"
|
"totp_auth_url": "TOTP YETKİ URL'si",
|
||||||
|
"use_testnet": "TestNet kullanın",
|
||||||
|
"address_and_silent_addresses": "Adres ve sessiz adresler",
|
||||||
|
"silent_addresses": "Sessiz adresler"
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,5 +700,8 @@
|
||||||
"ask_each_time": "Запитайте кожен раз",
|
"ask_each_time": "Запитайте кожен раз",
|
||||||
"buy_provider_unavailable": "В даний час постачальник недоступний.",
|
"buy_provider_unavailable": "В даний час постачальник недоступний.",
|
||||||
"do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.",
|
"do_not_have_enough_gas_asset": "У вас недостатньо ${currency}, щоб здійснити трансакцію з поточними умовами мережі блокчейн. Вам потрібно більше ${currency}, щоб сплатити комісію мережі блокчейн, навіть якщо ви надсилаєте інший актив.",
|
||||||
"totp_auth_url": "TOTP AUTH URL"
|
"totp_auth_url": "TOTP AUTH URL",
|
||||||
|
"use_testnet": "Використовуйте тестову мережу",
|
||||||
|
"address_and_silent_addresses": "Адреса та мовчазні адреси",
|
||||||
|
"silent_addresses": "Мовчазні адреси"
|
||||||
}
|
}
|
||||||
|
|
|
@ -692,5 +692,8 @@
|
||||||
"ask_each_time": "ہر بار پوچھیں",
|
"ask_each_time": "ہر بار پوچھیں",
|
||||||
"buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔",
|
"buy_provider_unavailable": "فراہم کنندہ فی الحال دستیاب نہیں ہے۔",
|
||||||
"do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔",
|
"do_not_have_enough_gas_asset": "آپ کے پاس موجودہ بلاکچین نیٹ ورک کی شرائط کے ساتھ لین دین کرنے کے لیے کافی ${currency} نہیں ہے۔ آپ کو بلاکچین نیٹ ورک کی فیس ادا کرنے کے لیے مزید ${currency} کی ضرورت ہے، چاہے آپ کوئی مختلف اثاثہ بھیج رہے ہوں۔",
|
||||||
"totp_auth_url": "TOTP AUTH URL"
|
"totp_auth_url": "TOTP AUTH URL",
|
||||||
|
"use_testnet": "ٹیسٹ نیٹ استعمال کریں",
|
||||||
|
"address_and_silent_addresses": "پتہ اور خاموش پتے",
|
||||||
|
"silent_addresses": "خاموش پتے"
|
||||||
}
|
}
|
||||||
|
|
|
@ -694,5 +694,8 @@
|
||||||
"ask_each_time": "Beere lọwọ kọọkan",
|
"ask_each_time": "Beere lọwọ kọọkan",
|
||||||
"buy_provider_unavailable": "Olupese lọwọlọwọ ko si.",
|
"buy_provider_unavailable": "Olupese lọwọlọwọ ko si.",
|
||||||
"do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.",
|
"do_not_have_enough_gas_asset": "O ko ni to ${currency} lati ṣe idunadura kan pẹlu awọn ipo nẹtiwọki blockchain lọwọlọwọ. O nilo diẹ sii ${currency} lati san awọn owo nẹtiwọọki blockchain, paapaa ti o ba nfi dukia miiran ranṣẹ.",
|
||||||
"totp_auth_url": "TOTP AUTH URL"
|
"totp_auth_url": "TOTP AUTH URL",
|
||||||
|
"use_testnet": "Lo tele",
|
||||||
|
"address_and_silent_addresses": "Adirẹsi ati awọn adirẹsi ipalọlọ",
|
||||||
|
"silent_addresses": "Awọn adirẹsi ipalọlọ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -699,5 +699,8 @@
|
||||||
"ask_each_time": "每次问",
|
"ask_each_time": "每次问",
|
||||||
"buy_provider_unavailable": "提供者目前不可用。",
|
"buy_provider_unavailable": "提供者目前不可用。",
|
||||||
"do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。",
|
"do_not_have_enough_gas_asset": "您没有足够的 ${currency} 来在当前的区块链网络条件下进行交易。即使您发送的是不同的资产,您也需要更多的 ${currency} 来支付区块链网络费用。",
|
||||||
"totp_auth_url": "TOTP 授权 URL"
|
"totp_auth_url": "TOTP 授权 URL",
|
||||||
|
"use_testnet": "使用TestNet",
|
||||||
|
"address_and_silent_addresses": "地址和无声地址",
|
||||||
|
"silent_addresses": "无声地址"
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
import 'package:cw_bitcoin/litecoin_wallet_service.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 bitcoinCwPart = "part 'cw_bitcoin.dart';";
|
||||||
const bitcoinContent = """
|
const bitcoinContent = """
|
||||||
|
@ -71,6 +72,8 @@ abstract class Bitcoin {
|
||||||
|
|
||||||
List<String> getAddresses(Object wallet);
|
List<String> getAddresses(Object wallet);
|
||||||
String getAddress(Object wallet);
|
String getAddress(Object wallet);
|
||||||
|
String getReceiveAddress(Object wallet);
|
||||||
|
btc.SilentPaymentAddress? getSilentAddress(Object wallet);
|
||||||
|
|
||||||
String formatterBitcoinAmountToString({required int amount});
|
String formatterBitcoinAmountToString({required int amount});
|
||||||
double formatterBitcoinAmountToDouble({required int amount});
|
double formatterBitcoinAmountToDouble({required int amount});
|
||||||
|
@ -729,4 +732,4 @@ class FakeSecureStorage extends SecureStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
await outputFile.writeAsString(output);
|
await outputFile.writeAsString(output);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue