mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-17 01:37:40 +00:00
Merge pull request #158 from cake-tech/CAKE-334-unspent-coins-control-application-logic
Cake 334 unspent coins control application logic
This commit is contained in:
commit
8f098e208c
39 changed files with 879 additions and 72 deletions
|
@ -1,4 +1,10 @@
|
|||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
|
||||
class BitcoinTransactionWrongBalanceException implements Exception {
|
||||
BitcoinTransactionWrongBalanceException(this.currency);
|
||||
|
||||
final CryptoCurrency currency;
|
||||
|
||||
@override
|
||||
String toString() => 'Wrong balance. Not enough BTC on your balance.';
|
||||
String toString() => 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
|
||||
class BitcoinUnspent {
|
||||
BitcoinUnspent(this.address, this.hash, this.value, this.vout);
|
||||
BitcoinUnspent(this.address, this.hash, this.value, this.vout)
|
||||
: isSending = true,
|
||||
isFrozen = false,
|
||||
note = '';
|
||||
|
||||
factory BitcoinUnspent.fromJSON(
|
||||
BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
||||
|
@ -15,4 +18,7 @@ class BitcoinUnspent {
|
|||
|
||||
bool get isP2wpkh =>
|
||||
address.address.startsWith('bc') || address.address.startsWith('ltc');
|
||||
bool isSending;
|
||||
bool isFrozen;
|
||||
String note;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
|
@ -17,6 +19,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
{@required String mnemonic,
|
||||
@required String password,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
List<BitcoinAddressRecord> initialAddresses,
|
||||
ElectrumBalance initialBalance,
|
||||
int accountIndex = 0})
|
||||
|
@ -24,6 +27,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: bitcoin.bitcoin,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance) {
|
||||
|
@ -38,6 +42,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
static Future<BitcoinWallet> open({
|
||||
@required String name,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
@required String password,
|
||||
}) async {
|
||||
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
|
||||
|
@ -46,6 +51,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
mnemonic: snp.mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialBalance: snp.balance,
|
||||
accountIndex: snp.accountIndex);
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
||||
|
@ -14,9 +15,10 @@ class BitcoinWalletService extends WalletService<
|
|||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
BitcoinWalletService(this.walletInfoSource);
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.bitcoin;
|
||||
|
@ -26,7 +28,8 @@ class BitcoinWalletService extends WalletService<
|
|||
final wallet = BitcoinWallet(
|
||||
mnemonic: await generateMnemonic(),
|
||||
password: credentials.password,
|
||||
walletInfo: credentials.walletInfo);
|
||||
walletInfo: credentials.walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
@ -42,7 +45,8 @@ class BitcoinWalletService extends WalletService<
|
|||
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||
orElse: () => null);
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo);
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
@ -67,7 +71,8 @@ class BitcoinWalletService extends WalletService<
|
|||
final wallet = BitcoinWallet(
|
||||
password: credentials.password,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo);
|
||||
walletInfo: credentials.walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:rxdart/subjects.dart';
|
||||
|
@ -39,6 +41,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
ElectrumWalletBase(
|
||||
{@required String password,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
@required List<BitcoinAddressRecord> initialAddresses,
|
||||
@required this.networkType,
|
||||
@required this.mnemonic,
|
||||
|
@ -56,9 +59,10 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
super(walletInfo) {
|
||||
this.electrumClient = electrumClient ?? ElectrumClient();
|
||||
this.walletInfo = walletInfo;
|
||||
this.unspentCoinsInfo = unspentCoinsInfo;
|
||||
transactionHistory =
|
||||
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
||||
_unspent = [];
|
||||
unspentCoins = [];
|
||||
_scripthashesUpdateSubject = {};
|
||||
}
|
||||
|
||||
|
@ -69,6 +73,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
final String mnemonic;
|
||||
|
||||
ElectrumClient electrumClient;
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
@override
|
||||
ElectrumWalletAddresses walletAddresses;
|
||||
|
@ -97,7 +102,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
|
||||
|
||||
final String _password;
|
||||
List<BitcoinUnspent> _unspent;
|
||||
List<BitcoinUnspent> unspentCoins;
|
||||
List<int> _feeRates;
|
||||
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
|
||||
bool _isTransactionUpdating;
|
||||
|
@ -116,7 +121,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
await updateTransactions();
|
||||
_subscribeForUpdates();
|
||||
await _updateBalance();
|
||||
await _updateUnspent();
|
||||
await updateUnspent();
|
||||
_feeRates = await electrumClient.feeRates();
|
||||
|
||||
Timer.periodic(const Duration(minutes: 1),
|
||||
|
@ -153,33 +158,16 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
const minAmount = 546;
|
||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||
final inputs = <BitcoinUnspent>[];
|
||||
final allAmountFee =
|
||||
calculateEstimatedFee(transactionCredentials.priority, null);
|
||||
final allAmount = balance.confirmed - allAmountFee;
|
||||
var fee = 0;
|
||||
final credentialsAmount = transactionCredentials.amount != null
|
||||
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
|
||||
: 0;
|
||||
final amount = transactionCredentials.amount == null ||
|
||||
allAmount - credentialsAmount < minAmount
|
||||
? allAmount
|
||||
: credentialsAmount;
|
||||
final txb = bitcoin.TransactionBuilder(network: networkType);
|
||||
final changeAddress = walletAddresses.address;
|
||||
var leftAmount = amount;
|
||||
var totalInputAmount = 0;
|
||||
var allInputsAmount = 0;
|
||||
|
||||
if (_unspent.isEmpty) {
|
||||
await _updateUnspent();
|
||||
if (unspentCoins.isEmpty) {
|
||||
await updateUnspent();
|
||||
}
|
||||
|
||||
for (final utx in _unspent) {
|
||||
leftAmount = leftAmount - utx.value;
|
||||
totalInputAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
|
||||
if (leftAmount <= 0) {
|
||||
break;
|
||||
for (final utx in unspentCoins) {
|
||||
if (utx.isSending) {
|
||||
allInputsAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,18 +175,56 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
throw BitcoinTransactionNoInputsException();
|
||||
}
|
||||
|
||||
final totalAmount = amount + fee;
|
||||
fee = transactionCredentials.amount != null
|
||||
? feeAmountForPriority(transactionCredentials.priority, inputs.length,
|
||||
amount == allAmount ? 1 : 2)
|
||||
: allAmountFee;
|
||||
final allAmountFee =
|
||||
feeAmountForPriority(transactionCredentials.priority, inputs.length, 1);
|
||||
final allAmount = allInputsAmount - allAmountFee;
|
||||
|
||||
if (totalAmount > balance.confirmed) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
final credentialsAmount = transactionCredentials.amount != null
|
||||
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
|
||||
: 0;
|
||||
final amount = transactionCredentials.amount == null ||
|
||||
allAmount - credentialsAmount < minAmount
|
||||
? allAmount
|
||||
: credentialsAmount;
|
||||
final fee = transactionCredentials.amount == null || amount == allAmount
|
||||
? allAmountFee
|
||||
: calculateEstimatedFee(transactionCredentials.priority, amount);
|
||||
|
||||
if (fee == 0) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
if (amount <= 0 || totalInputAmount < amount) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
final totalAmount = amount + fee;
|
||||
|
||||
if (totalAmount > balance.confirmed || totalAmount > allInputsAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
final txb = bitcoin.TransactionBuilder(network: networkType);
|
||||
final changeAddress = walletAddresses.address;
|
||||
var leftAmount = totalAmount;
|
||||
var totalInputAmount = 0;
|
||||
|
||||
inputs.clear();
|
||||
|
||||
for (final utx in unspentCoins) {
|
||||
if (utx.isSending) {
|
||||
leftAmount = leftAmount - utx.value;
|
||||
totalInputAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
|
||||
if (leftAmount <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs.isEmpty) {
|
||||
throw BitcoinTransactionNoInputsException();
|
||||
}
|
||||
|
||||
if (amount <= 0 || totalInputAmount < totalAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
txb.setVersion(1);
|
||||
|
@ -273,17 +299,26 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
if (amount != null) {
|
||||
int totalValue = 0;
|
||||
|
||||
for (final input in _unspent) {
|
||||
for (final input in unspentCoins) {
|
||||
if (totalValue >= amount) {
|
||||
break;
|
||||
}
|
||||
|
||||
totalValue += input.value;
|
||||
inputsCount += 1;
|
||||
if (input.isSending) {
|
||||
totalValue += input.value;
|
||||
inputsCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalValue < amount) return 0;
|
||||
} else {
|
||||
inputsCount = _unspent.length;
|
||||
for (final input in unspentCoins) {
|
||||
if (input.isSending) {
|
||||
inputsCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If send all, then we have no change value
|
||||
return feeAmountForPriority(
|
||||
priority, inputsCount, amount != null ? 2 : 1);
|
||||
|
@ -315,13 +350,74 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
Future<String> makePath() async =>
|
||||
pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
Future<void> _updateUnspent() async {
|
||||
Future<void> updateUnspent() async {
|
||||
final unspent = await Future.wait(walletAddresses
|
||||
.addresses.map((address) => electrumClient
|
||||
.getListUnspentWithAddress(address.address, networkType)
|
||||
.then((unspent) => unspent
|
||||
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
|
||||
_unspent = unspent.expand((e) => e).toList();
|
||||
unspentCoins = unspent.expand((e) => e).toList();
|
||||
|
||||
if (unspentCoinsInfo.isEmpty) {
|
||||
unspentCoins.forEach((coin) => _addCoinInfo(coin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId.contains(id) && element.hash.contains(coin.hash));
|
||||
|
||||
if (coinInfoList.isNotEmpty) {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
} else {
|
||||
_addCoinInfo(coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await _refreshUnspentCoinsInfo();
|
||||
}
|
||||
|
||||
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
walletId: id,
|
||||
hash: coin.hash,
|
||||
isFrozen: coin.isFrozen,
|
||||
isSending: coin.isSending,
|
||||
note: coin.note
|
||||
);
|
||||
|
||||
await unspentCoinsInfo.add(newInfo);
|
||||
}
|
||||
|
||||
Future<void> _refreshUnspentCoinsInfo() async {
|
||||
try {
|
||||
final List<dynamic> keys = <dynamic>[];
|
||||
final currentWalletUnspentCoins = unspentCoinsInfo.values
|
||||
.where((element) => element.walletId.contains(id));
|
||||
|
||||
if (currentWalletUnspentCoins.isNotEmpty) {
|
||||
currentWalletUnspentCoins.forEach((element) {
|
||||
final existUnspentCoins = unspentCoins
|
||||
?.where((coin) => element.hash.contains(coin?.hash));
|
||||
|
||||
if (existUnspentCoins?.isEmpty ?? true) {
|
||||
keys.add(element.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (keys.isNotEmpty) {
|
||||
await unspentCoinsInfo.deleteAll(keys);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<ElectrumTransactionInfo> fetchTransactionInfo(
|
||||
|
@ -372,7 +468,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|||
_scripthashesUpdateSubject[sh].listen((event) async {
|
||||
try {
|
||||
await _updateBalance();
|
||||
await _updateUnspent();
|
||||
await updateUnspent();
|
||||
await updateTransactions();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/litecoin_wallet_addresses.dart';
|
||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
|
||||
|
@ -19,6 +21,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
{@required String mnemonic,
|
||||
@required String password,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
List<BitcoinAddressRecord> initialAddresses,
|
||||
ElectrumBalance initialBalance,
|
||||
int accountIndex = 0})
|
||||
|
@ -26,6 +29,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: litecoinNetwork,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance) {
|
||||
|
@ -41,6 +45,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
static Future<LitecoinWallet> open({
|
||||
@required String name,
|
||||
@required WalletInfo walletInfo,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
@required String password,
|
||||
}) async {
|
||||
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
|
||||
|
@ -49,6 +54,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
mnemonic: snp.mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialBalance: snp.balance,
|
||||
accountIndex: snp.accountIndex);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:io';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
||||
|
@ -14,9 +15,10 @@ class LitecoinWalletService extends WalletService<
|
|||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
LitecoinWalletService(this.walletInfoSource);
|
||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.litecoin;
|
||||
|
@ -26,7 +28,8 @@ class LitecoinWalletService extends WalletService<
|
|||
final wallet = LitecoinWallet(
|
||||
mnemonic: await generateMnemonic(),
|
||||
password: credentials.password,
|
||||
walletInfo: credentials.walletInfo);
|
||||
walletInfo: credentials.walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
|
@ -43,7 +46,8 @@ class LitecoinWalletService extends WalletService<
|
|||
(info) => info.id == WalletBase.idFor(name, getType()),
|
||||
orElse: () => null);
|
||||
final wallet = await LitecoinWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo);
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
@ -68,7 +72,8 @@ class LitecoinWalletService extends WalletService<
|
|||
final wallet = LitecoinWallet(
|
||||
password: credentials.password,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo);
|
||||
walletInfo: credentials.walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
|
32
lib/bitcoin/unspent_coins_info.dart
Normal file
32
lib/bitcoin/unspent_coins_info.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'unspent_coins_info.g.dart';
|
||||
|
||||
@HiveType(typeId: UnspentCoinsInfo.typeId)
|
||||
class UnspentCoinsInfo extends HiveObject {
|
||||
UnspentCoinsInfo({
|
||||
this.walletId,
|
||||
this.hash,
|
||||
this.isFrozen,
|
||||
this.isSending,
|
||||
this.note});
|
||||
|
||||
static const typeId = 9;
|
||||
static const boxName = 'Unspent';
|
||||
static const boxKey = 'unspentBoxKey';
|
||||
|
||||
@HiveField(0)
|
||||
String walletId;
|
||||
|
||||
@HiveField(1)
|
||||
String hash;
|
||||
|
||||
@HiveField(2)
|
||||
bool isFrozen;
|
||||
|
||||
@HiveField(3)
|
||||
bool isSending;
|
||||
|
||||
@HiveField(4)
|
||||
String note;
|
||||
}
|
47
lib/di.dart
47
lib/di.dart
|
@ -1,5 +1,6 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/entities/biometric_auth.dart';
|
||||
|
@ -39,6 +40,8 @@ import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
|||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||
|
@ -76,6 +79,9 @@ import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
|||
import 'package:cake_wallet/view_model/support_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
|
@ -125,6 +131,7 @@ Box<Template> _templates;
|
|||
Box<ExchangeTemplate> _exchangeTemplates;
|
||||
Box<TransactionDescription> _transactionDescriptionBox;
|
||||
Box<Order> _ordersSource;
|
||||
Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
|
||||
|
||||
Future setup(
|
||||
{Box<WalletInfo> walletInfoSource,
|
||||
|
@ -134,7 +141,8 @@ Future setup(
|
|||
Box<Template> templates,
|
||||
Box<ExchangeTemplate> exchangeTemplates,
|
||||
Box<TransactionDescription> transactionDescriptionBox,
|
||||
Box<Order> ordersSource}) async {
|
||||
Box<Order> ordersSource,
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfoSource}) async {
|
||||
_walletInfoSource = walletInfoSource;
|
||||
_nodeSource = nodeSource;
|
||||
_contactSource = contactSource;
|
||||
|
@ -143,6 +151,7 @@ Future setup(
|
|||
_exchangeTemplates = exchangeTemplates;
|
||||
_transactionDescriptionBox = transactionDescriptionBox;
|
||||
_ordersSource = ordersSource;
|
||||
_unspentCoinsInfoSource = unspentCoinsInfoSource;
|
||||
|
||||
if (!_isSetupFinished) {
|
||||
getIt.registerSingletonAsync<SharedPreferences>(
|
||||
|
@ -446,9 +455,11 @@ Future setup(
|
|||
|
||||
getIt.registerFactory(() => MoneroWalletService(_walletInfoSource));
|
||||
|
||||
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
|
||||
getIt.registerFactory(() =>
|
||||
BitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
|
||||
|
||||
getIt.registerFactory(() => LitecoinWalletService(_walletInfoSource));
|
||||
getIt.registerFactory(() =>
|
||||
LitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
|
||||
|
||||
getIt.registerFactoryParam<WalletService, WalletType, void>(
|
||||
(WalletType param1, __) {
|
||||
|
@ -584,5 +595,35 @@ Future setup(
|
|||
|
||||
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
||||
return UnspentCoinsListViewModel(
|
||||
wallet: wallet,
|
||||
unspentCoinsInfo: _unspentCoinsInfoSource);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => UnspentCoinsListPage(
|
||||
unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>()
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel,
|
||||
UnspentCoinsItem, UnspentCoinsListViewModel>((item, model) =>
|
||||
UnspentCoinsDetailsViewModel(
|
||||
unspentCoinsItem: item,
|
||||
unspentCoinsListViewModel: model));
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsPage, List, void>(
|
||||
(List args, _) {
|
||||
final item = args.first as UnspentCoinsItem;
|
||||
final unspentCoinsListViewModel = args[1] as UnspentCoinsListViewModel;
|
||||
|
||||
return UnspentCoinsDetailsPage(
|
||||
unspentCoinsDetailsViewModel:
|
||||
getIt.get<UnspentCoinsDetailsViewModel>(
|
||||
param1: item,
|
||||
param2: unspentCoinsListViewModel));
|
||||
});
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/entities/language_service.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -75,6 +76,10 @@ Future<void> main() async {
|
|||
Hive.registerAdapter(OrderAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
|
||||
Hive.registerAdapter(UnspentCoinsInfoAdapter());
|
||||
}
|
||||
|
||||
final secureStorage = FlutterSecureStorage();
|
||||
final transactionDescriptionsBoxKey = await getEncryptionKey(
|
||||
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
|
||||
|
@ -95,6 +100,8 @@ Future<void> main() async {
|
|||
final templates = await Hive.openBox<Template>(Template.boxName);
|
||||
final exchangeTemplates =
|
||||
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
|
||||
final unspentCoinsInfoSource =
|
||||
await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
|
||||
await initialSetup(
|
||||
sharedPreferences: await SharedPreferences.getInstance(),
|
||||
nodes: nodes,
|
||||
|
@ -102,6 +109,7 @@ Future<void> main() async {
|
|||
contactSource: contacts,
|
||||
tradesSource: trades,
|
||||
ordersSource: orders,
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource,
|
||||
// fiatConvertationService: fiatConvertationService,
|
||||
templates: templates,
|
||||
exchangeTemplates: exchangeTemplates,
|
||||
|
@ -134,6 +142,7 @@ Future<void> initialSetup(
|
|||
@required Box<Template> templates,
|
||||
@required Box<ExchangeTemplate> exchangeTemplates,
|
||||
@required Box<TransactionDescription> transactionDescriptions,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
|
||||
FlutterSecureStorage secureStorage,
|
||||
int initialMigrationVersion = 15}) async {
|
||||
LanguageService.loadLocaleList();
|
||||
|
@ -153,7 +162,8 @@ Future<void> initialSetup(
|
|||
templates: templates,
|
||||
exchangeTemplates: exchangeTemplates,
|
||||
transactionDescriptionBox: transactionDescriptions,
|
||||
ordersSource: ordersSource);
|
||||
ordersSource: ordersSource,
|
||||
unspentCoinsInfoSource: unspentCoinsInfoSource);
|
||||
await bootstrap(navigatorKey);
|
||||
monero_wallet.onStartup();
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ class Palette {
|
|||
static const Color dullGray = Color.fromRGBO(98, 98, 98, 1.0);
|
||||
static const Color protectiveBlue = Color.fromRGBO(33, 148, 255, 1.0);
|
||||
static const Color darkBlue = Color.fromRGBO(109, 128, 178, 1.0);
|
||||
static const Color paleCornflowerBlue = Color.fromRGBO(185, 196, 237, 1.0);
|
||||
}
|
||||
|
||||
class PaletteDark {
|
||||
|
|
|
@ -11,9 +11,12 @@ import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
|
|||
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
|
@ -364,6 +367,18 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<SupportPage>());
|
||||
|
||||
case Routes.unspentCoinsList:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<UnspentCoinsListPage>());
|
||||
|
||||
case Routes.unspentCoinsDetails:
|
||||
final args = settings.arguments as List;
|
||||
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
getIt.get<UnspentCoinsDetailsPage>(
|
||||
param1: args));
|
||||
|
||||
default:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Scaffold(
|
||||
|
|
|
@ -55,4 +55,6 @@ class Routes {
|
|||
static const orderDetails = '/order_details';
|
||||
static const preOrder = '/pre_order';
|
||||
static const buyWebView = '/buy_web_view';
|
||||
static const unspentCoinsList = '/unspent_coins_list';
|
||||
static const unspentCoinsDetails = '/unspent_coins_details';
|
||||
}
|
|
@ -430,7 +430,31 @@ class SendPage extends BasePage {
|
|||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
)),
|
||||
if (sendViewModel.isElectrumWallet) Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.unspentCoinsList),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).coin_control,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white)),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
@ -576,8 +600,8 @@ class SendPage extends BasePage {
|
|||
isLoading: sendViewModel.state is IsExecutingState ||
|
||||
sendViewModel.state is TransactionCommitting,
|
||||
isDisabled:
|
||||
false // FIXME !(syncStore.status is SyncedSyncStatus),
|
||||
);
|
||||
false // FIXME !(syncStore.status is SyncedSyncStatus),
|
||||
);
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_switch_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class UnspentCoinsDetailsPage extends BasePage {
|
||||
UnspentCoinsDetailsPage({this.unspentCoinsDetailsViewModel});
|
||||
|
||||
@override
|
||||
String get title => S.current.unspent_coins_details_title;
|
||||
|
||||
final UnspentCoinsDetailsViewModel unspentCoinsDetailsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => unspentCoinsDetailsViewModel.items.length,
|
||||
itemBuilder: (_, __, index) {
|
||||
final item = unspentCoinsDetailsViewModel.items[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return StandartListRow(
|
||||
title: '${item.title}:',
|
||||
value: item.value);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is UnspentCoinsSwitchItem) {
|
||||
return Observer(builder: (_) => UnspentCoinsSwitchRow(
|
||||
title: item.title,
|
||||
switchValue: item.switchValue(),
|
||||
onSwitchValueChange: item.onSwitchValueChange
|
||||
));
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
116
lib/src/screens/unspent_coins/unspent_coins_list_page.dart
Normal file
116
lib/src/screens/unspent_coins/unspent_coins_list_page.dart
Normal file
|
@ -0,0 +1,116 @@
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class UnspentCoinsListPage extends BasePage {
|
||||
UnspentCoinsListPage({this.unspentCoinsListViewModel});
|
||||
|
||||
@override
|
||||
String get title => S.current.unspent_coins_title;
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
final questionImage = Image.asset('assets/images/question_mark.png',
|
||||
color: Theme.of(context).primaryTextTheme.title.color);
|
||||
|
||||
return SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
child: FlatButton(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
padding: EdgeInsets.all(0),
|
||||
onPressed: () => showUnspentCoinsAlert(context),
|
||||
child: questionImage),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) =>
|
||||
UnspentCoinsListForm(unspentCoinsListViewModel);
|
||||
}
|
||||
|
||||
class UnspentCoinsListForm extends StatefulWidget {
|
||||
UnspentCoinsListForm(this.unspentCoinsListViewModel);
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
UnspentCoinsListFormState createState() =>
|
||||
UnspentCoinsListFormState(unspentCoinsListViewModel);
|
||||
}
|
||||
|
||||
class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
UnspentCoinsListFormState(this.unspentCoinsListViewModel);
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
|
||||
}
|
||||
|
||||
void afterLayout(dynamic _) {
|
||||
showUnspentCoinsAlert(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.fromLTRB(24, 12, 24, 24),
|
||||
child: Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
itemCount: unspentCoinsListViewModel.items.length,
|
||||
separatorBuilder: (_, __) =>
|
||||
SizedBox(height: 15),
|
||||
itemBuilder: (_, int index) {
|
||||
return Observer(builder: (_) {
|
||||
final item = unspentCoinsListViewModel.items[index];
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () =>
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.unspentCoinsDetails,
|
||||
arguments: [item, unspentCoinsListViewModel]),
|
||||
child: UnspentCoinsListItem(
|
||||
address: item.address,
|
||||
amount: item.amount,
|
||||
isSending: item.isSending,
|
||||
onCheckBoxTap: item.isFrozen
|
||||
? null
|
||||
: () async {
|
||||
item.isSending = !item.isSending;
|
||||
await unspentCoinsListViewModel
|
||||
.saveUnspentCoinInfo(item);}));
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showUnspentCoinsAlert(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: '',
|
||||
alertContent: 'Information about unspent coins',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class UnspentCoinsListItem extends StatelessWidget {
|
||||
UnspentCoinsListItem({
|
||||
@required this.address,
|
||||
@required this.amount,
|
||||
@required this.isSending,
|
||||
@required this.onCheckBoxTap,
|
||||
});
|
||||
|
||||
static const amountColor = Palette.darkBlueCraiola;
|
||||
static const addressColor = Palette.darkGray;
|
||||
static const selectedItemColor = Palette.paleCornflowerBlue;
|
||||
static const unselectedItemColor = Palette.moderateLavender;
|
||||
|
||||
final String address;
|
||||
final String amount;
|
||||
final bool isSending;
|
||||
final Function() onCheckBoxTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final itemColor = isSending? selectedItemColor : unselectedItemColor;
|
||||
|
||||
return Container(
|
||||
height: 62,
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
color: itemColor),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: GestureDetector(
|
||||
onTap: () => onCheckBoxTap?.call(),
|
||||
child: Container(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.caption
|
||||
.color,
|
||||
width: 1.0),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(8.0)),
|
||||
color: Theme.of(context).backgroundColor),
|
||||
child: isSending
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Colors.blue,
|
||||
size: 20.0,
|
||||
)
|
||||
: Offstage(),
|
||||
)
|
||||
)
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AutoSizeText(
|
||||
amount,
|
||||
style: TextStyle(
|
||||
color: amountColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600
|
||||
),
|
||||
maxLines: 1,
|
||||
),
|
||||
AutoSizeText(
|
||||
address,
|
||||
style: TextStyle(
|
||||
color: addressColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
maxLines: 1,
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import 'package:cake_wallet/src/widgets/standart_switch.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UnspentCoinsSwitchRow extends StatelessWidget {
|
||||
UnspentCoinsSwitchRow(
|
||||
{this.title,
|
||||
this.titleFontSize = 14,
|
||||
this.switchValue,
|
||||
this.onSwitchValueChange});
|
||||
|
||||
final String title;
|
||||
final double titleFontSize;
|
||||
final bool switchValue;
|
||||
final void Function(bool value) onSwitchValueChange;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(title,
|
||||
style: TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme.overline.color),
|
||||
textAlign: TextAlign.left),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: StandartSwitch(
|
||||
value: switchValue,
|
||||
onTaped: () => onSwitchValueChange(!switchValue))
|
||||
)
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -44,9 +44,6 @@ class StandardCheckboxState extends State<StandardCheckbox> {
|
|||
Container(
|
||||
height: 24.0,
|
||||
width: 24.0,
|
||||
margin: EdgeInsets.only(
|
||||
right: 10.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
|
@ -65,14 +62,17 @@ class StandardCheckboxState extends State<StandardCheckbox> {
|
|||
)
|
||||
: Offstage(),
|
||||
),
|
||||
Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
if (caption.isNotEmpty) Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.title
|
||||
.color),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ abstract class SendViewModelBase with Store {
|
|||
_settingsStore.priority[_wallet.type] = priorities.first;
|
||||
}
|
||||
|
||||
isElectrumWallet = _wallet is ElectrumWallet;
|
||||
|
||||
_setCryptoNumMaximumFractionDigits();
|
||||
}
|
||||
|
||||
|
@ -179,6 +181,9 @@ abstract class SendViewModelBase with Store {
|
|||
@observable
|
||||
PendingTransaction pendingTransaction;
|
||||
|
||||
@observable
|
||||
bool isElectrumWallet;
|
||||
|
||||
@computed
|
||||
String get balance => _wallet.balance.formattedAvailableBalance ?? '0.0';
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_switch_item.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'unspent_coins_details_view_model.g.dart';
|
||||
|
||||
class UnspentCoinsDetailsViewModel = UnspentCoinsDetailsViewModelBase
|
||||
with _$UnspentCoinsDetailsViewModel;
|
||||
|
||||
abstract class UnspentCoinsDetailsViewModelBase with Store {
|
||||
UnspentCoinsDetailsViewModelBase({
|
||||
this.unspentCoinsItem, this.unspentCoinsListViewModel}) {
|
||||
|
||||
final amount = unspentCoinsItem.amount ?? '';
|
||||
final address = unspentCoinsItem.address ?? '';
|
||||
isFrozen = unspentCoinsItem.isFrozen ?? false;
|
||||
note = unspentCoinsItem.note ?? '';
|
||||
|
||||
items = [
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_amount,
|
||||
value: amount
|
||||
),
|
||||
StandartListItem(
|
||||
title: S.current.widgets_address,
|
||||
value: address
|
||||
),
|
||||
TextFieldListItem(
|
||||
title: S.current.note_tap_to_change,
|
||||
value: note,
|
||||
onSubmitted: (value) {
|
||||
unspentCoinsItem.note = value;
|
||||
unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
|
||||
}),
|
||||
UnspentCoinsSwitchItem(
|
||||
title: S.current.freeze,
|
||||
value: '',
|
||||
switchValue: () => isFrozen,
|
||||
onSwitchValueChange: (value) async {
|
||||
isFrozen = value;
|
||||
unspentCoinsItem.isFrozen = value;
|
||||
if (value) {
|
||||
unspentCoinsItem.isSending = !value;
|
||||
}
|
||||
await unspentCoinsListViewModel.saveUnspentCoinInfo(unspentCoinsItem);
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@observable
|
||||
bool isFrozen;
|
||||
|
||||
@observable
|
||||
String note;
|
||||
|
||||
final UnspentCoinsItem unspentCoinsItem;
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
List<TransactionDetailsListItem> items;
|
||||
}
|
33
lib/view_model/unspent_coins/unspent_coins_item.dart
Normal file
33
lib/view_model/unspent_coins/unspent_coins_item.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'unspent_coins_item.g.dart';
|
||||
|
||||
class UnspentCoinsItem = UnspentCoinsItemBase with _$UnspentCoinsItem;
|
||||
|
||||
abstract class UnspentCoinsItemBase with Store {
|
||||
UnspentCoinsItemBase({
|
||||
this.address,
|
||||
this.amount,
|
||||
this.hash,
|
||||
this.isFrozen,
|
||||
this.note,
|
||||
this.isSending});
|
||||
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@observable
|
||||
String amount;
|
||||
|
||||
@observable
|
||||
String hash;
|
||||
|
||||
@observable
|
||||
bool isFrozen;
|
||||
|
||||
@observable
|
||||
String note;
|
||||
|
||||
@observable
|
||||
bool isSending;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'unspent_coins_list_view_model.g.dart';
|
||||
|
||||
class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel;
|
||||
|
||||
abstract class UnspentCoinsListViewModelBase with Store {
|
||||
UnspentCoinsListViewModelBase({
|
||||
@required WalletBase wallet,
|
||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo}) {
|
||||
_unspentCoinsInfo = unspentCoinsInfo;
|
||||
_wallet = wallet as ElectrumWallet;
|
||||
_wallet.updateUnspent();
|
||||
}
|
||||
|
||||
ElectrumWallet _wallet;
|
||||
Box<UnspentCoinsInfo> _unspentCoinsInfo;
|
||||
|
||||
@computed
|
||||
ObservableList<UnspentCoinsItem> get items =>
|
||||
ObservableList.of(_wallet.unspentCoins.map((elem) {
|
||||
final amount = bitcoinAmountToString(amount: elem.value) +
|
||||
' ${_wallet.currency.title}';
|
||||
|
||||
return UnspentCoinsItem(
|
||||
address: elem.address.address,
|
||||
amount: amount,
|
||||
hash: elem.hash,
|
||||
isFrozen: elem.isFrozen,
|
||||
note: elem.note,
|
||||
isSending: elem.isSending
|
||||
);
|
||||
}));
|
||||
|
||||
Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
|
||||
try {
|
||||
final info = _unspentCoinsInfo.values
|
||||
.firstWhere((element) => element.walletId.contains(_wallet.id) &&
|
||||
element.hash.contains(item.hash));
|
||||
|
||||
info.isFrozen = item.isFrozen;
|
||||
info.isSending = item.isSending;
|
||||
info.note = item.note;
|
||||
|
||||
await info.save();
|
||||
await _wallet.updateUnspent();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
12
lib/view_model/unspent_coins/unspent_coins_switch_item.dart
Normal file
12
lib/view_model/unspent_coins/unspent_coins_switch_item.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
|
||||
class UnspentCoinsSwitchItem extends TransactionDetailsListItem {
|
||||
UnspentCoinsSwitchItem({
|
||||
String title,
|
||||
String value,
|
||||
this.switchValue,
|
||||
this.onSwitchValueChange}) : super(title: title, value: value);
|
||||
|
||||
final bool Function() switchValue;
|
||||
final void Function(bool value) onSwitchValueChange;
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Wenn diese Brieftasche einen 12-Wort-Seed hat und in Cake erstellt wurde, zahlen Sie KEINE Bitcoins in diese Brieftasche ein. Alle auf diese Wallet übertragenen BTC können verloren gehen. Erstellen Sie eine neue 24-Wort-Wallet (tippen Sie auf das Menü oben rechts, wählen Sie Wallets, wählen Sie Neue Wallet erstellen und dann Bitcoin) und verschieben Sie Ihre BTC SOFORT dorthin. Neue (24-Wort-)BTC-Wallets von Cake sind sicher",
|
||||
"do_not_show_me": "Zeig mir das nicht noch einmal",
|
||||
|
||||
"unspent_coins_title" : "Nicht ausgegebene Münzen",
|
||||
"unspent_coins_details_title" : "Details zu nicht ausgegebenen Münzen",
|
||||
"freeze" : "Einfrieren",
|
||||
"coin_control" : "Münzkontrolle (optional)",
|
||||
|
||||
"address_detected" : "Adresse erkannt",
|
||||
"address_from_domain" : "Sie haben die Adresse von der unaufhaltsamen Domain ${domain} erhalten"
|
||||
}
|
||||
|
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "If this wallet has a 12-word seed and was created in Cake, DO NOT deposit Bitcoin into this wallet. Any BTC transferred to this wallet may be lost. Create a new 24-word wallet (tap the menu at the top right, select Wallets, choose Create New Wallet, then select Bitcoin) and IMMEDIATELY move your BTC there. New (24-word) BTC wallets from Cake are secure",
|
||||
"do_not_show_me": "Do not show me this again",
|
||||
|
||||
"unspent_coins_title" : "Unspent coins",
|
||||
"unspent_coins_details_title" : "Unspent coins details",
|
||||
"freeze" : "Freeze",
|
||||
"coin_control" : "Coin control (optional)",
|
||||
|
||||
"address_detected" : "Address detected",
|
||||
"address_from_domain" : "You got address from unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Si esta billetera tiene una semilla de 12 palabras y se creó en Cake, NO deposite Bitcoin en esta billetera. Cualquier BTC transferido a esta billetera se puede perder. Cree una nueva billetera de 24 palabras (toque el menú en la parte superior derecha, seleccione Monederos, elija Crear nueva billetera, luego seleccione Bitcoin) e INMEDIATAMENTE mueva su BTC allí. Las nuevas carteras BTC (24 palabras) de Cake son seguras",
|
||||
"do_not_show_me": "no me muestres esto otra vez",
|
||||
|
||||
"unspent_coins_title" : "Monedas no gastadas",
|
||||
"unspent_coins_details_title" : "Detalles de monedas no gastadas",
|
||||
"freeze" : "Congelar",
|
||||
"coin_control" : "Control de monedas (opcional)",
|
||||
|
||||
"address_detected" : "Dirección detectada",
|
||||
"address_from_domain" : "Tienes la dirección de unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "अगर इस वॉलेट में 12 शब्दों का बीज है और इसे केक में बनाया गया है, तो इस वॉलेट में बिटकॉइन जमा न करें। इस वॉलेट में स्थानांतरित किया गया कोई भी बीटीसी खो सकता है। एक नया 24-शब्द वॉलेट बनाएं (ऊपर दाईं ओर स्थित मेनू पर टैप करें, वॉलेट चुनें, नया वॉलेट बनाएं चुनें, फिर बिटकॉइन चुनें) और तुरंत अपना बीटीसी वहां ले जाएं। केक से नए (24-शब्द) बीटीसी वॉलेट सुरक्षित हैं",
|
||||
"do_not_show_me": "मुझे यह फिर न दिखाएं",
|
||||
|
||||
"unspent_coins_title" : "खर्च न किए गए सिक्के",
|
||||
"unspent_coins_details_title" : "अव्ययित सिक्कों का विवरण",
|
||||
"freeze" : "फ्रीज",
|
||||
"coin_control" : "सिक्का नियंत्रण (वैकल्पिक)",
|
||||
|
||||
"address_detected" : "पता लग गया",
|
||||
"address_from_domain" : "आपको अजेय डोमेन ${domain} से पता मिला है"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Ako ovaj novčanik sadrži sjeme od 12 riječi i stvoren je u Torti, NEMOJTE polagati Bitcoin u ovaj novčanik. Bilo koji BTC prebačen u ovaj novčanik može se izgubiti. Stvorite novi novčanik od 24 riječi (taknite izbornik u gornjem desnom dijelu, odaberite Novčanici, odaberite Stvori novi novčanik, a zatim odaberite Bitcoin) i ODMAH premjestite svoj BTC tamo. Novi BTC novčanici (s 24 riječi) tvrtke Cake sigurni su",
|
||||
"do_not_show_me": "Ne pokazuj mi ovo više",
|
||||
|
||||
"unspent_coins_title" : "Nepotrošeni novčići",
|
||||
"unspent_coins_details_title" : "Nepotrošeni detalji o novčićima",
|
||||
"freeze" : "Zamrznuti",
|
||||
"coin_control" : "Kontrola novca (nije obavezno)",
|
||||
|
||||
"address_detected" : "Adresa je otkrivena",
|
||||
"address_from_domain" : "Dobili ste adresu od unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Se questo portafoglio ha un seme di 12 parole ed è stato creato in Cake, NON depositare Bitcoin in questo portafoglio. Qualsiasi BTC trasferito su questo portafoglio potrebbe andare perso. Crea un nuovo portafoglio di 24 parole (tocca il menu in alto a destra, seleziona Portafogli, scegli Crea nuovo portafoglio, quindi seleziona Bitcoin) e sposta IMMEDIATAMENTE lì il tuo BTC. I nuovi portafogli BTC (24 parole) di Cake sono sicuri",
|
||||
"do_not_show_me": "Non mostrarmelo di nuovo",
|
||||
|
||||
"unspent_coins_title" : "Monete non spese",
|
||||
"unspent_coins_details_title" : "Dettagli sulle monete non spese",
|
||||
"freeze" : "Congelare",
|
||||
"coin_control" : "Controllo monete (opzionale)",
|
||||
|
||||
"address_detected" : "Indirizzo rilevato",
|
||||
"address_from_domain" : "Hai l'indirizzo da unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "このウォレットに 12 ワードのシードがあり、Cake で作成された場合、このウォレットにビットコインを入金しないでください。 このウォレットに転送された BTC は失われる可能性があります。 新しい 24 ワードのウォレットを作成し (右上のメニューをタップし、[ウォレット]、[新しいウォレットの作成]、[ビットコイン] の順に選択)、すぐに BTC をそこに移動します。 Cake の新しい (24 ワード) BTC ウォレットは安全です",
|
||||
"do_not_show_me": "また僕にこれを見せないでください",
|
||||
|
||||
"unspent_coins_title" : "未使用のコイン",
|
||||
"unspent_coins_details_title" : "未使用のコインの詳細",
|
||||
"freeze" : "氷結",
|
||||
"coin_control" : "コインコントロール(オプション)",
|
||||
|
||||
"address_detected" : "アドレスが検出されました",
|
||||
"address_from_domain" : "あなたはからアドレスを得ました unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "이 지갑에 12 단어 시드가 있고 Cake에서 생성 된 경우이 지갑에 비트 코인을 입금하지 마십시오. 이 지갑으로 전송 된 모든 BTC는 손실 될 수 있습니다. 새로운 24 단어 지갑을 생성하고 (오른쪽 상단의 메뉴를 탭하고 지갑을 선택한 다음 새 지갑 생성을 선택한 다음 비트 코인을 선택하십시오) 즉시 BTC를 그곳으로 이동하십시오. Cake의 새로운 (24 단어) BTC 지갑은 안전합니다",
|
||||
"do_not_show_me": "나를 다시 표시하지 않음",
|
||||
|
||||
"unspent_coins_title" : "사용하지 않은 동전",
|
||||
"unspent_coins_details_title" : "사용하지 않은 동전 세부 정보",
|
||||
"freeze" : "얼다",
|
||||
"coin_control" : "코인 제어 (옵션)",
|
||||
|
||||
"address_detected" : "주소 감지",
|
||||
"address_from_domain" : "주소는 unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Als deze portemonnee een seed van 12 woorden heeft en is gemaakt in Cake, stort dan GEEN Bitcoin in deze portemonnee. Elke BTC die naar deze portemonnee is overgebracht, kan verloren gaan. Maak een nieuwe portemonnee van 24 woorden (tik op het menu rechtsboven, selecteer Portefeuilles, kies Nieuwe portemonnee maken en selecteer vervolgens Bitcoin) en verplaats je BTC ONMIDDELLIJK daar. Nieuwe (24-woorden) BTC-portefeuilles van Cake zijn veilig",
|
||||
"do_not_show_me": "laat me dit niet opnieuw zien",
|
||||
|
||||
"unspent_coins_title" : "Ongebruikte munten",
|
||||
"unspent_coins_details_title" : "Details van niet-uitgegeven munten",
|
||||
"freeze" : "Bevriezen",
|
||||
"coin_control" : "Muntcontrole (optioneel)",
|
||||
|
||||
"address_detected" : "Adres gedetecteerd",
|
||||
"address_from_domain" : "Je adres is van unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Jeśli ten portfel ma 12-wyrazowy seed i został utworzony w Cake, NIE Wpłacaj Bitcoina do tego portfela. Wszelkie BTC przeniesione do tego portfela mogą zostać utracone. Utwórz nowy portfel z 24 słowami (dotknij menu w prawym górnym rogu, wybierz Portfele, wybierz Utwórz nowy portfel, a następnie Bitcoin) i NATYCHMIAST przenieś tam swoje BTC. Nowe (24 słowa) portfele BTC firmy Cake są bezpieczne",
|
||||
"do_not_show_me": "Nie pokazuj mi tego ponownie",
|
||||
|
||||
"unspent_coins_title" : "Niewydane monety",
|
||||
"unspent_coins_details_title" : "Szczegóły niewydanych monet",
|
||||
"freeze" : "Zamrażać",
|
||||
"coin_control" : "Kontrola monet (opcjonalnie)",
|
||||
|
||||
"address_detected" : "Wykryto adres",
|
||||
"address_from_domain" : "Dostałeś adres od unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Se esta carteira tiver uma semente de 12 palavras e foi criada no Cake, NÃO deposite Bitcoin nesta carteira. Qualquer BTC transferido para esta carteira pode ser perdido. Crie uma nova carteira de 24 palavras (toque no menu no canto superior direito, selecione Carteiras, escolha Criar Nova Carteira e selecione Bitcoin) e mova IMEDIATAMENTE seu BTC para lá. As novas carteiras BTC (24 palavras) da Cake são seguras",
|
||||
"do_not_show_me": "não me mostre isso novamente",
|
||||
|
||||
"unspent_coins_title" : "Moedas não gastas",
|
||||
"unspent_coins_details_title" : "Detalhes de moedas não gastas",
|
||||
"freeze" : "Congelar",
|
||||
"coin_control" : "Controle de moedas (opcional)",
|
||||
|
||||
"address_detected" : "Endereço detectado",
|
||||
"address_from_domain" : "Você obteve o endereço de unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Если этот кошелек имеет мнемоническую фразу из 12 слов и был создан в Cake, НЕ переводите биткойны на этот кошелек. Любые BTC, переведенные на этот кошелек, могут быть потеряны. Создайте новый кошелек с мнемоническои фразы из 24 слов (коснитесь меню в правом верхнем углу, выберите «Кошельки», выберите «Создать новый кошелек», затем выберите «Bitcoin») и НЕМЕДЛЕННО переведите туда свои BTC. Новые (24 слова) кошельки BTC от Cake безопасны",
|
||||
"do_not_show_me": "Не показывай мне это больше",
|
||||
|
||||
"unspent_coins_title" : "Неизрасходованные монеты",
|
||||
"unspent_coins_details_title" : "Сведения о неизрасходованных монетах",
|
||||
"freeze" : "Заморозить",
|
||||
"coin_control" : "Контроль монет (необязательно)",
|
||||
|
||||
"address_detected" : "Обнаружен адрес",
|
||||
"address_from_domain" : "Вы получили адрес из unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "Якщо цей гаманець має мнемонічну фразу з 12 слів і був створений у Cake, НЕ переводьте біткойни на цей гаманець. Будь-які BTC, переведений на цей гаманець, можуть бути втраченими. Створіть новий гаманець з мнемонічною фразою з 24 слів (торкніться меню у верхньому правому куті, виберіть Гаманці, виберіть Створити новий гаманець, потім виберіть Bitcoin) і НЕГАЙНО переведіть туди свії BTC. Нові (з мнемонічною фразою з 24 слів) гаманці BTC від Cake надійно захищені",
|
||||
"do_not_show_me": "Не показуй мені це знову",
|
||||
|
||||
"unspent_coins_title" : "Невитрачені монети",
|
||||
"unspent_coins_details_title" : "Відомості про невитрачені монети",
|
||||
"freeze" : "Заморозити",
|
||||
"coin_control" : "Контроль монет (необов’язково)",
|
||||
|
||||
"address_detected" : "Виявлено адресу",
|
||||
"address_from_domain" : "Ви отримали адресу від unstoppable domain ${domain}"
|
||||
}
|
|
@ -485,6 +485,11 @@
|
|||
"outdated_electrum_wallet_receive_warning": "如果这个钱包有一个 12 字的种子并且是在 Cake 中创建的,不要将比特币存入这个钱包。 任何转移到此钱包的 BTC 都可能丢失。 创建一个新的 24 字钱包(点击右上角的菜单,选择钱包,选择创建新钱包,然后选择比特币)并立即将您的 BTC 移到那里。 Cake 的新(24 字)BTC 钱包是安全的",
|
||||
"do_not_show_me": "不再提示",
|
||||
|
||||
"unspent_coins_title" : "未使用的硬幣",
|
||||
"unspent_coins_details_title" : "未使用代幣詳情",
|
||||
"freeze" : "凍結",
|
||||
"coin_control" : "硬幣控制(可選)",
|
||||
|
||||
"address_detected" : "檢測到地址",
|
||||
"address_from_domain" : "您有以下地址 unstoppable domain ${domain}"
|
||||
}
|
Loading…
Reference in a new issue